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
+357
View File
@@ -0,0 +1,357 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load("//tensorflow:tensorflow.bzl", "clean_dep")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load("//tensorflow/lite:build_def.bzl", "tflite_copts", "tflite_copts_warnings")
load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite_combined")
# copybara:uncomment_begin(google-only)
# load("//tensorflow/lite/experimental/perfetto_profiling:build_def.bzl", "tflite_perfetto_copts")
# copybara:uncomment_end
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
common_copts = tflite_copts() + tflite_copts_warnings()
cc_library(
name = "profiler",
hdrs = [
"buffered_profiler.h",
"noop_profiler.h",
"profiler.h",
],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
deps = [
":profile_buffer",
"//tensorflow/lite/core/api",
],
)
cc_test(
name = "profiler_test",
srcs = ["profiler_test.cc"],
deps = [
":profile_buffer",
":profiler",
"//tensorflow/lite/core/api",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "atrace_profiler",
srcs = ["atrace_profiler.cc"],
hdrs = ["atrace_profiler.h"],
copts = common_copts,
visibility = ["//visibility:private"],
deps = [
"//tensorflow/lite/core/api",
],
)
cc_test(
name = "atrace_profiler_test",
srcs = ["atrace_profiler_test.cc"],
deps = [
":atrace_profiler",
"@com_google_googletest//:gtest_main",
],
)
objc_library(
name = "signpost_profiler",
hdrs = ["signpost_profiler.h"],
copts = common_copts,
non_arc_srcs = ["signpost_profiler.mm"],
tags = ["apple"],
deps = [
"//tensorflow/lite/core/api",
],
)
cc_library(
name = "platform_profiler",
srcs = ["platform_profiler.cc"],
hdrs = ["platform_profiler.h"],
compatible_with = get_compatible_with_portable(),
copts =
# copybara:uncomment_begin(google-only)
# tflite_perfetto_copts() +
# copybara:uncomment_end
common_copts,
deps = [
"//tensorflow/lite/core/api",
] + select({
# copybara:uncomment_begin(google-only)
# clean_dep("//tensorflow/lite/experimental/perfetto_profiling:enable_tflite_perfetto_profiler_explicit_true"): [
# "//tensorflow/lite/experimental/perfetto_profiling:perfetto_profiler",
# ],
# copybara:uncomment_end
"//tensorflow:android": [":atrace_profiler"],
"//tensorflow:ios": [":signpost_profiler"],
"//conditions:default": [],
}),
)
cc_library(
name = "profile_buffer",
srcs = [
"profile_buffer.cc",
],
hdrs = ["profile_buffer.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
deps = [
":memory_info",
":time",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/core/api",
],
)
cc_test(
name = "profile_buffer_test",
srcs = ["profile_buffer_test.cc"],
deps = [
":profile_buffer",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "time",
srcs = ["time.cc"],
hdrs = ["time.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
)
cc_test(
name = "time_test",
srcs = ["time_test.cc"],
deps = [
":time",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "memory_info",
srcs = ["memory_info.cc"],
hdrs = ["memory_info.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
)
cc_test(
name = "memory_info_test",
srcs = ["memory_info_test.cc"],
tags = [
# Some low-level checks, like heap size check, may break in asan, msan
# and tsan. So, disable such tests.
"noasan",
"nomsan",
"notsan",
# TODO(b/166227284): Fix the test for Android.
"tflite_not_portable_android",
],
deps = [
":memory_info",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "memory_usage_monitor",
srcs = ["memory_usage_monitor.cc"],
hdrs = ["memory_usage_monitor.h"],
copts = common_copts,
deps = [
":memory_info",
"//tensorflow/lite:minimal_logging",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "memory_usage_monitor_test",
srcs = ["memory_usage_monitor_test.cc"],
deps = [
":memory_info",
":memory_usage_monitor",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "memory_latency_logger",
srcs = ["memory_latency_logger.cc"],
hdrs = ["memory_latency_logger.h"],
deps = [
":memory_usage_monitor",
"//tensorflow/lite:framework_stable",
"//tensorflow/lite/tools:logging",
"@com_google_absl//absl/strings:string_view",
"@com_google_absl//absl/time",
],
)
cc_library(
name = "profile_summary_formatter",
srcs = ["profile_summary_formatter.cc"],
hdrs = ["profile_summary_formatter.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
deps = [
"//tensorflow/core/util:stats_calculator_portable",
"//tensorflow/lite/profiling/proto:profiling_info_cc",
"//tensorflow/lite/tools:logging",
],
)
cc_test(
name = "profile_summary_formatter_test",
srcs = ["profile_summary_formatter_test.cc"],
deps = [
":profile_summary_formatter",
"//tensorflow/core/util:stats_calculator_portable",
"//tensorflow/lite/profiling/proto:profiling_info_cc",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "profile_summarizer",
srcs = ["profile_summarizer.cc"],
hdrs = ["profile_summarizer.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
deps = [
":memory_info",
":profile_buffer",
":profile_summary_formatter",
"//tensorflow/core/util:stats_calculator_portable",
"//tensorflow/lite:framework",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core:framework_stable",
"//tensorflow/lite/core/api",
],
)
cc_library(
name = "root_profiler",
srcs = ["root_profiler.cc"],
hdrs = ["root_profiler.h"],
compatible_with = get_compatible_with_portable(),
copts = common_copts,
deps = ["//tensorflow/lite/core/api"],
)
cc_test(
name = "root_profiler_test",
srcs = ["root_profiler_test.cc"],
deps = [
":root_profiler",
"//tensorflow/lite/core/api",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "subgraph_tensor_profiler",
srcs = ["subgraph_tensor_profiler.cc"],
hdrs = ["subgraph_tensor_profiler.h"],
copts = common_copts,
deps = [
"//tensorflow/lite:framework_stable",
"//tensorflow/lite/core:subgraph",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"@tsl//tsl/platform:path",
],
)
cc_library(
name = "model_runtime_info",
srcs = ["model_runtime_info.cc"],
hdrs = ["model_runtime_info.h"],
copts = common_copts,
deps = [
"//tensorflow/lite:framework_stable",
"//tensorflow/lite:optional_debug_tools",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/core:cc_api_stable",
"//tensorflow/lite/core:subgraph",
"//tensorflow/lite/profiling/proto:model_runtime_info_cc",
"//tensorflow/lite/schema:schema_fbs",
"//tensorflow/lite/tools:logging",
"@com_google_absl//absl/strings:string_view",
"@com_google_protobuf//:protobuf",
],
)
cc_test(
name = "model_runtime_info_test",
srcs = ["model_runtime_info_test.cc"],
deps = [
":model_runtime_info",
":profiler",
"//tensorflow/lite:framework",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core/api",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate_hdrs_only",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/profiling/proto:model_runtime_info_cc",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "subgraph_tensor_profiler_test",
srcs = ["subgraph_tensor_profiler_test.cc"],
deps = [
":subgraph_tensor_profiler",
"//tensorflow/lite/core:subgraph",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/kernels:subgraph_test_util",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "profile_summarizer_test",
srcs = ["profile_summarizer_test.cc"],
copts = common_copts,
deps = [
":profile_summarizer",
":profiler",
"//tensorflow/lite:framework",
"//tensorflow/lite/c:c_api_types",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/kernels:subgraph_test_util",
"//tensorflow/lite/kernels:test_util",
"//tensorflow/lite/schema:schema_fbs",
"@com_google_googletest//:gtest_main",
],
)
tflite_portable_test_suite_combined(
combine_conditions = {"deps": ["@com_google_googletest//:gtest_main"]},
enable_ios_test_suite = True,
)
@@ -0,0 +1,115 @@
/* Copyright 2020 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/profiling/atrace_profiler.h"
#include <dlfcn.h>
#include "tensorflow/lite/core/api/profiler.h"
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#include <string>
#include <type_traits>
namespace tflite {
namespace profiling {
// Profiler reporting to ATrace.
class ATraceProfiler : public tflite::Profiler {
public:
using FpIsEnabled = std::add_pointer<bool()>::type;
using FpBeginSection = std::add_pointer<void(const char*)>::type;
using FpEndSection = std::add_pointer<void()>::type;
ATraceProfiler() {
handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
if (handle_) {
// Use dlsym() to prevent crashes on devices running Android 5.1
// (API level 22) or lower.
atrace_is_enabled_ =
reinterpret_cast<FpIsEnabled>(dlsym(handle_, "ATrace_isEnabled"));
atrace_begin_section_ = reinterpret_cast<FpBeginSection>(
dlsym(handle_, "ATrace_beginSection"));
atrace_end_section_ =
reinterpret_cast<FpEndSection>(dlsym(handle_, "ATrace_endSection"));
if (!atrace_is_enabled_ || !atrace_begin_section_ ||
!atrace_end_section_) {
dlclose(handle_);
handle_ = nullptr;
}
}
}
~ATraceProfiler() override {
if (handle_) {
dlclose(handle_);
}
}
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) override {
if (handle_ && atrace_is_enabled_()) {
// Note: When recording an OPERATOR_INVOKE_EVENT, we have recorded the op
// name
// as tag, node index as event_metadata1 and subgraph index as
// event_metadata2. See the macro TFLITE_SCOPED_TAGGED_OPERATOR_PROFILE
// defined in tensorflow/lite/core/api/profiler.h for details.
// Regardless the 'event_type', we encode the perfetto event name as
// tag@event_metadata1/event_metadata2. In case of OPERATOR_INVOKE_EVENT,
// the perfetto event name will be op_name@node_index/subgraph_index
std::string trace_event_tag = tag;
trace_event_tag += "@";
trace_event_tag += std::to_string(event_metadata1) + "/" +
std::to_string(event_metadata2);
atrace_begin_section_(trace_event_tag.c_str());
}
return 0;
}
void EndEvent(uint32_t event_handle) override {
if (handle_) {
atrace_end_section_();
}
}
private:
// Handle to libandroid.so library. Null if not supported.
void* handle_;
FpIsEnabled atrace_is_enabled_;
FpBeginSection atrace_begin_section_;
FpEndSection atrace_end_section_;
};
std::unique_ptr<tflite::Profiler> MaybeCreateATraceProfiler() {
#if defined(TFLITE_ENABLE_DEFAULT_PROFILER)
return std::unique_ptr<tflite::Profiler>(new ATraceProfiler());
#else // TFLITE_ENABLE_DEFAULT_PROFILER
#if defined(__ANDROID__)
constexpr char kTraceProp[] = "debug.tflite.trace";
char trace_enabled[PROP_VALUE_MAX] = "";
int length = __system_property_get(kTraceProp, trace_enabled);
if (length == 1 && trace_enabled[0] == '1') {
return std::unique_ptr<tflite::Profiler>(new ATraceProfiler());
}
#endif // __ANDROID__
return nullptr;
#endif // TFLITE_ENABLE_DEFAULT_PROFILER
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,33 @@
/* Copyright 2020 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_PROFILING_ATRACE_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_ATRACE_PROFILER_H_
#include <memory>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
// Creates a profiler which reports the traced events to the Android ATrace.
// Nullptr will be returned if the Android system property 'debug.tflite.trace'
// is not set or the property value is not 1.
std::unique_ptr<tflite::Profiler> MaybeCreateATraceProfiler();
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_ATRACE_PROFILER_H_
@@ -0,0 +1,55 @@
/* Copyright 2020 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/profiling/atrace_profiler.h"
#if defined(__ANDROID__)
#include <sys/system_properties.h>
#endif
#include <gtest/gtest.h>
namespace tflite {
namespace profiling {
namespace {
TEST(ATraceProfilerTest, MaybeCreateATraceProfiler) {
auto initial_state_profiler = MaybeCreateATraceProfiler();
#if !defined(TFLITE_ENABLE_DEFAULT_PROFILER)
EXPECT_EQ(nullptr, initial_state_profiler.get());
#else
EXPECT_NE(nullptr, initial_state_profiler.get());
#endif
#if defined(__ANDROID__)
if (__system_property_set("debug.tflite.trace", "1") == 0) {
auto on_state_profiler = MaybeCreateATraceProfiler();
EXPECT_NE(nullptr, on_state_profiler.get());
}
if (__system_property_set("debug.tflite.trace", "0") == 0) {
auto off_state_profiler = MaybeCreateATraceProfiler();
#if !defined(TFLITE_ENABLE_DEFAULT_PROFILER)
EXPECT_EQ(nullptr, off_state_profiler.get());
#else
EXPECT_NE(nullptr, off_state_profiler.get());
#endif
}
#endif // __ANDROID__
}
} // namespace
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,145 @@
/* 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_PROFILING_BUFFERED_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_BUFFERED_PROFILER_H_
#include <cstdint>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/profile_buffer.h"
namespace tflite {
namespace profiling {
// Controls whether profiling is enabled or disabled and collects profiles.
// TFLite is used on platforms that don't have posix threads, so the profiler is
// kept as simple as possible. It is designed to be used only on a single
// thread.
//
// Profiles are collected using Scoped*Profile objects that begin and end a
// profile event.
// An example usage is shown in the example below:
//
// Say Worker class has a DoWork method and we are interested in profiling
// the overall execution time for DoWork and time spent in Task1 and Task2
// functions.
//
// class Worker {
// public:
// void DoWork() {
// ScopedProfile(&controller, "DoWork");
// Task1();
// Task2();
// .....
// }
//
// void Task1() {
// ScopedProfile(&controller, "Task1");
// ....
// }
//
// void Task2() {
// ScopedProfile(&controller, "Task2");
// }
//
// Profiler profiler;
// }
//
// We instrument the functions that need to be profiled.
//
// Profile can be collected by enable profiling and then getting profile
// events.
//
// void ProfileWorker() {
// Worker worker;
// worker.profiler.EnableProfiling();
// worker.DoWork();
// worker.profiler.DisableProfiling();
// // Profiling is complete, extract profiles.
// auto profile_events = worker.profiler.GetProfiles();
// }
//
//
class BufferedProfiler : public tflite::Profiler {
public:
BufferedProfiler(uint32_t max_num_initial_entries,
bool allow_dynamic_buffer_increase)
: buffer_(max_num_initial_entries, false /*enabled*/,
allow_dynamic_buffer_increase),
supported_event_types_(
~(static_cast<uint64_t>(
EventType::GENERAL_RUNTIME_INSTRUMENTATION_EVENT) |
static_cast<uint64_t>(EventType::TELEMETRY_EVENT) |
static_cast<uint64_t>(EventType::TELEMETRY_REPORT_SETTINGS) |
static_cast<uint64_t>(EventType::TELEMETRY_DELEGATE_EVENT) |
static_cast<uint64_t>(
EventType::TELEMETRY_DELEGATE_REPORT_SETTINGS))) {}
explicit BufferedProfiler(uint32_t max_num_entries)
: BufferedProfiler(max_num_entries,
false /*allow_dynamic_buffer_increase*/) {}
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) override {
if (!ShouldAddEvent(event_type)) return kInvalidEventHandle;
return buffer_.BeginEvent(tag, event_type, event_metadata1,
event_metadata2);
}
void EndEvent(uint32_t event_handle) override {
buffer_.EndEvent(event_handle);
}
void EndEvent(uint32_t event_handle, int64_t event_metadata1,
int64_t event_metadata2) override {
buffer_.EndEvent(event_handle, &event_metadata1, &event_metadata2);
}
void AddEvent(const char* tag, EventType event_type, uint64_t elapsed_time,
int64_t event_metadata1, int64_t event_metadata2) override {
if (!ShouldAddEvent(event_type)) return;
buffer_.AddEvent(tag, event_type, elapsed_time, event_metadata1,
event_metadata2);
}
void StartProfiling() { buffer_.SetEnabled(true); }
void StopProfiling() { buffer_.SetEnabled(false); }
void Reset() { buffer_.Reset(); }
std::vector<const ProfileEvent*> GetProfileEvents() {
std::vector<const ProfileEvent*> profile_events;
profile_events.reserve(buffer_.Size());
for (size_t i = 0; i < buffer_.Size(); i++) {
profile_events.push_back(buffer_.At(i));
}
return profile_events;
}
protected:
bool ShouldAddEvent(EventType event_type) {
return (static_cast<uint64_t>(event_type) & supported_event_types_) != 0;
}
private:
ProfileBuffer* GetProfileBuffer() { return &buffer_; }
ProfileBuffer buffer_;
const uint64_t supported_event_types_;
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_BUFFERED_PROFILER_H_
+168
View File
@@ -0,0 +1,168 @@
/* 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/profiling/memory_info.h"
#include <stddef.h>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <ostream>
#include <sstream>
#include <string>
#ifdef __linux__
#include <malloc.h>
#include <sys/resource.h>
#include <sys/time.h>
#elif defined(__APPLE__)
#include <mach/mach.h>
#include <malloc/malloc.h>
#elif defined(_WIN32)
#include <windows.h>
// psapi must be included after windows.h.
#include <psapi.h>
#endif
namespace tflite {
namespace profiling {
namespace memory {
const size_t MemoryUsage::kValueNotSet = 0;
namespace {
#if defined(__linux__)
// Returns the current VM swap in kilobytes on Linux.
int64_t GetCurrentVmSwapKb() {
std::ifstream status_file("/proc/self/status");
if (!status_file.is_open()) {
return -1;
}
std::string line;
while (std::getline(status_file, line)) {
if (line.rfind("VmSwap:", 0) == 0) {
std::stringstream ss(line);
std::string key;
int64_t value_kb;
// The line format is "VmSwap: 1234 kB"
// We can extract the key ("VmSwap:") and the numeric value ("1234").
ss >> key >> value_kb;
if (!ss.fail()) {
return value_kb;
} else {
return -1; // Indicate parsing error
}
}
}
// If the VmSwap line is not found, it means 0 swap is being used.
return 0;
}
#endif
} // namespace
bool MemoryUsage::IsSupported() {
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
return true;
#endif
return false;
}
MemoryUsage GetMemoryUsage() {
MemoryUsage result;
#ifdef __linux__
rusage res;
if (getrusage(RUSAGE_SELF, &res) == 0) {
result.mem_footprint_kb = res.ru_maxrss;
int64_t vm_swap_kb = GetCurrentVmSwapKb();
if (vm_swap_kb >= 0) {
result.private_footprint_bytes = (vm_swap_kb + res.ru_maxrss) * 1024;
}
}
#if defined(__NO_MALLINFO__) || !defined(__GLIBC__) || \
defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
defined(THREAD_SANITIZER)
result.total_allocated_bytes = -1;
result.in_use_allocated_bytes = -1;
#elif __GLIBC_MINOR__ >= 33
const auto mem = mallinfo2();
result.total_allocated_bytes = mem.arena;
result.in_use_allocated_bytes = mem.uordblks;
#else
const auto mem = mallinfo();
result.total_allocated_bytes = mem.arena;
result.in_use_allocated_bytes = mem.uordblks;
#endif // defined(__NO_MALLINFO__) || !defined(__GLIBC__) || \
// defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) ||
// defined(THREAD_SANITIZER)
#elif defined(__APPLE__)
struct task_vm_info vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
auto status = task_info(mach_task_self(), TASK_VM_INFO,
reinterpret_cast<task_info_t>(&vm_info), &count);
if (status == KERN_SUCCESS) {
result.mem_footprint_kb =
static_cast<int64_t>(vm_info.phys_footprint / 1024.0);
// TODO: b/421171145 - Consider subtracting shared_resident_kb.
result.private_footprint_bytes = vm_info.phys_footprint;
}
struct mstats stats = mstats();
result.total_allocated_bytes = stats.bytes_total;
result.in_use_allocated_bytes = stats.bytes_used;
#elif defined(_WIN32)
PROCESS_MEMORY_COUNTERS_EX process_memory_counters;
HANDLE process_handle = GetCurrentProcess();
if (process_handle != nullptr &&
GetProcessMemoryInfo(process_handle,
(PROCESS_MEMORY_COUNTERS*)&process_memory_counters,
sizeof(process_memory_counters))) {
result.mem_footprint_kb = process_memory_counters.WorkingSetSize / 1024;
result.private_footprint_bytes = process_memory_counters.PrivateUsage;
} else {
result.mem_footprint_kb = -1;
result.private_footprint_bytes = -1;
}
CloseHandle(process_handle);
result.total_allocated_bytes = -1;
result.in_use_allocated_bytes = -1;
#ifdef USE_WIN32_HEAP_SUMMARY
HANDLE process_heap = GetProcessHeap();
if (process_heap != nullptr) {
HEAP_SUMMARY heap_summary;
heap_summary.cb = sizeof(heap_summary);
if (HeapSummary(process_heap, 0, &heap_summary)) {
result.total_allocated_bytes = heap_summary.cbCommitted;
result.in_use_allocated_bytes = heap_summary.cbAllocated;
}
}
#endif // USE_WIN32_HEAP_SUMMARY
#endif // __linux__
return result;
}
void MemoryUsage::AllStatsToStream(std::ostream* stream) const {
*stream << "max resident set size/physical footprint = "
<< mem_footprint_kb / 1000.0 << " MB, total non-mmapped heap size = "
<< total_allocated_bytes / 1000.0 / 1000.0
<< " MB, in-use heap size = "
<< in_use_allocated_bytes / 1000.0 / 1000.0
<< " MB, private footprint = "
<< private_footprint_bytes / 1000.0 / 1000.0 << " MB";
}
} // namespace memory
} // namespace profiling
} // namespace tflite
+141
View File
@@ -0,0 +1,141 @@
/* 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_PROFILING_MEMORY_INFO_H_
#define TENSORFLOW_LITE_PROFILING_MEMORY_INFO_H_
#include <stddef.h>
#include <cstdint>
#include <ostream>
#include <sstream>
namespace tflite {
namespace profiling {
namespace memory {
struct MemoryUsage {
static const size_t kValueNotSet;
// Indicates whether obtaining memory usage is supported on the platform, thus
// indicating whether the values defined in this struct make sense or not.
// Note that even if this returns true, some of the fields in the struct may
// not be supported by GetMemoryUsage(); in such cases, unsupported fields
// will be set to kValueNotSet (zero) or -1.
static bool IsSupported();
MemoryUsage()
: mem_footprint_kb(kValueNotSet),
total_allocated_bytes(kValueNotSet),
in_use_allocated_bytes(kValueNotSet),
private_footprint_bytes(kValueNotSet) {}
// The memory footprint (in kilobytes).
//
// For Linux:
// This is the maximum memory size (in kilobytes) occupied by an OS process
// that is held in main memory (RAM). Such memory usage information is
// generally referred as resident set size (rss). This is an alias to
// rusage::ru_maxrss.
//
// For Mac:
// This is the physical memory footprint (in kilobytes). This is an alias to
// task_vm_info::phys_footprint.
// Per kern/task.c, physical footprint is the sum of:
// + (internal - alternate_accounting)
// + (internal_compressed - alternate_accounting_compressed)
// + iokit_mapped
// + purgeable_nonvolatile
// + purgeable_nonvolatile_compressed
// + page_table
//
// For Windows:
// This is the current memory size (in kilobytes) occupied by the OS process
// that is held in main memory (RAM). This is generally referred to as the
// working set size. This is an alias to
// PROCESS_MEMORY_COUNTERS::WorkingSetSize.
int64_t mem_footprint_kb;
// Total non-mmapped heap space allocated from system in bytes.
// For Linux, this is an alias to mallinfo::arena.
// For Mac, this is an alias to mstats::bytes_total
// For Windows, this is an alias to HEAP_SUMMARY::cbCommitted
// for the default process heap.
//
// This does not count mmapped heap space, nor does it count non-heap
// uses of memory such as other mmapped space, thread stacks, globals,
// code, etc.
size_t total_allocated_bytes;
// Total allocated (including mmapped) heap bytes that are in use
// (i.e. excluding those have been freed).
// For Linux, this is an alias to mallinfo::uordblks.
// For Mac, this is an alias to mstats::bytes_used
// For Windows, this is an alias to HEAP_SUMMARY::cbAllocated
// for the default process heap.
//
// This does not count non-heap uses of mmap, nor does it count other
// non-heap uses of memory such as thread stacks, globals, code, etc.
size_t in_use_allocated_bytes;
// Private footprint (in kilobytes).
//
// For Linux this is the rusage::ru_maxrss + VmSwap.
// For Mac this is the task_vm_info::phys_footprint.
// For Windows this is the PrivateUsage.
size_t private_footprint_bytes;
MemoryUsage operator+(MemoryUsage const& obj) const {
MemoryUsage res;
res.mem_footprint_kb = mem_footprint_kb + obj.mem_footprint_kb;
res.total_allocated_bytes =
total_allocated_bytes + obj.total_allocated_bytes;
res.in_use_allocated_bytes =
in_use_allocated_bytes + obj.in_use_allocated_bytes;
res.private_footprint_bytes =
private_footprint_bytes + obj.private_footprint_bytes;
return res;
}
MemoryUsage operator-(MemoryUsage const& obj) const {
MemoryUsage res;
res.mem_footprint_kb = mem_footprint_kb - obj.mem_footprint_kb;
res.total_allocated_bytes =
total_allocated_bytes - obj.total_allocated_bytes;
res.in_use_allocated_bytes =
in_use_allocated_bytes - obj.in_use_allocated_bytes;
res.private_footprint_bytes =
private_footprint_bytes - obj.private_footprint_bytes;
return res;
}
void AllStatsToStream(std::ostream* stream) const;
friend std::ostream& operator<<(std::ostream& stream,
const MemoryUsage& obj) {
obj.AllStatsToStream(&stream);
return stream;
}
};
// Return the memory usage from the system.
// Note: this works on Linux, Mac, Windows, Android and iOS. It does not work
// in a WASM environment.
MemoryUsage GetMemoryUsage();
} // namespace memory
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_MEMORY_INFO_H_
@@ -0,0 +1,128 @@
/* 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/profiling/memory_info.h"
#include <memory>
#include <sstream>
#include <string>
#include <gtest/gtest.h>
namespace tflite {
namespace profiling {
namespace memory {
TEST(MemoryUsage, AddAndSub) {
MemoryUsage mem1, mem2;
mem1.mem_footprint_kb = 5;
mem1.total_allocated_bytes = 7000;
mem1.in_use_allocated_bytes = 2000;
mem1.private_footprint_bytes = 1000;
mem2.mem_footprint_kb = 3;
mem2.total_allocated_bytes = 7000;
mem2.in_use_allocated_bytes = 4000;
mem2.private_footprint_bytes = 500;
const auto add_mem = mem1 + mem2;
EXPECT_EQ(8, add_mem.mem_footprint_kb);
EXPECT_EQ(14000, add_mem.total_allocated_bytes);
EXPECT_EQ(6000, add_mem.in_use_allocated_bytes);
EXPECT_EQ(1500, add_mem.private_footprint_bytes);
const auto sub_mem = mem1 - mem2;
EXPECT_EQ(2, sub_mem.mem_footprint_kb);
EXPECT_EQ(0, sub_mem.total_allocated_bytes);
EXPECT_EQ(-2000, sub_mem.in_use_allocated_bytes);
EXPECT_EQ(500, sub_mem.private_footprint_bytes);
}
TEST(MemoryUsage, GetMemoryUsage) {
MemoryUsage result;
EXPECT_EQ(MemoryUsage::kValueNotSet, result.mem_footprint_kb);
EXPECT_EQ(MemoryUsage::kValueNotSet, result.total_allocated_bytes);
EXPECT_EQ(MemoryUsage::kValueNotSet, result.in_use_allocated_bytes);
EXPECT_EQ(MemoryUsage::kValueNotSet, result.private_footprint_bytes);
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
// Just allocate some space in heap so that we have some meaningful
// memory usage to report.
constexpr int size = 10 * 1024 * 1024;
std::unique_ptr<unsigned char[]> byte_array(new unsigned char[size]);
for (int i = 0; i < size; ++i) {
byte_array[i] = i % 256;
}
result = GetMemoryUsage();
// Use the heap object that we allocated, so that the compiler can't
// (so easily) optimize it away.
for (int i = 0; i < size; ++i) {
EXPECT_EQ(byte_array[i], i % 256);
}
EXPECT_GE(result.mem_footprint_kb, size / 1024);
#if (defined(__linux__) && !defined(ADDRESS_SANITIZER) && \
!defined(MEMORY_SANITIZER) && !defined(THREAD_SANITIZER)) || \
(defined(__APPLE__) && !defined(THREAD_SANITIZER)) || defined(_WIN32)
EXPECT_GE(result.total_allocated_bytes, size);
EXPECT_NE(result.total_allocated_bytes, -1);
EXPECT_GE(result.in_use_allocated_bytes, size);
EXPECT_NE(result.in_use_allocated_bytes, -1);
#else
// The mallinfo() function, which is used on Linux, returns invalid
// results when address/memory/thread sanitizer is enabled, e.g.
// <https://github.com/google/sanitizers/issues/1845>, so the
// *_allocated_bytes fields are not supported in those cases,
// and should be set to either -1 or kValueNotSet(0).
// For Apple platforms, the mstats() function returns invalid results when
// thread sanitizer is enabled.
if (result.total_allocated_bytes != -1) {
EXPECT_EQ(result.total_allocated_bytes, MemoryUsage::kValueNotSet);
}
if (result.in_use_allocated_bytes != -1) {
EXPECT_EQ(result.in_use_allocated_bytes, MemoryUsage::kValueNotSet);
}
#endif // (defined(__linux__) && !defined(ADDRESS_SANITIZER) && \
// !defined(MEMORY_SANITIZER) && !defined(THREAD_SANITIZER)) || \
// (defined(__APPLE__) && !defined(THREAD_SANITIZER)) || defined(_WIN32)
EXPECT_GE(result.private_footprint_bytes, size);
#endif // defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
}
// The main aim of this test is just to exercise the code for
// the ostream operator << and verify that it doesn't crash.
// There's not much that we can usefully assert about the resulting message
// here without making the test too brittle, so we just verify that
// it generates a non-empty message.
TEST(MemoryUsage, OutputMemoryUsageToStream) {
MemoryUsage memory_usage = GetMemoryUsage();
std::stringstream stream;
stream << memory_usage;
std::string message = stream.str();
EXPECT_STRNE(message.c_str(), "");
}
TEST(MemoryUsage, IsSupported) {
#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32)
EXPECT_TRUE(MemoryUsage::IsSupported());
#else
EXPECT_FALSE(MemoryUsage::IsSupported());
#endif
}
} // namespace memory
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,99 @@
/* Copyright 2025 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/profiling/memory_latency_logger.h"
#include <algorithm>
#include <iomanip>
#include <ios>
#include <memory>
#include <sstream>
#include <string>
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tensorflow/lite/profiling/memory_usage_monitor.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace profiling {
namespace memory {
MemoryLatencyLogger::MemoryLatencyLogger() {
mem_monitor_ =
std::make_unique<MemoryUsageMonitor>(/*sampling_interval_ms=*/50);
}
void MemoryLatencyLogger::Start() {
if (start_ != absl::UnixEpoch()) {
TFLITE_LOG(INFO) << "MemoryLatencyLogger start called multiple times.";
return;
}
start_ = absl::Now();
mem_monitor_->Start();
}
void MemoryLatencyLogger::Stop(absl::string_view log_message) {
if (start_ == absl::UnixEpoch()) {
TFLITE_LOG(INFO)
<< "MemoryLatencyLogger hasn't started yet or has stopped!";
return;
}
absl::Time stop = absl::Now();
mem_monitor_->Stop();
int space_count =
35 - log_message.size(); // used for better user readability.
std::string space(std::max(space_count, 0), '-');
std::stringstream message_stream;
message_stream << log_message << " " << space << " latency: " << std::fixed
<< std::setprecision(1)
<< absl::ToDoubleMilliseconds(stop - start_) << " ms,";
// Check each value before logging. If the value is not available, or if
// the value is < 0, log "unknown". Sometimes this can happen on machines that
// only support mallinfo() and not mallinfo2(). mallinfo() uses an int to
// store the current in-use memory, which can overflow if the program
// allocates more than 2GB of memory.
if (mem_monitor_->GetPeakMemUsageInMB() < 0) {
message_stream << " peak alloc: unknown,";
} else {
message_stream << " peak alloc: " << mem_monitor_->GetPeakMemUsageInMB()
<< " MB,";
}
if (mem_monitor_->GetPeakInUseMemoryInMB() < 0) {
message_stream << " peak in-use: unknown,";
} else {
message_stream << " peak in-use: " << mem_monitor_->GetPeakInUseMemoryInMB()
<< " MB,";
}
if (mem_monitor_->GetCurrentInUseMemoryInMB() < 0) {
message_stream << " current in-use: unknown,";
} else {
message_stream << " current in-use: "
<< mem_monitor_->GetCurrentInUseMemoryInMB() << " MB,";
}
if (mem_monitor_->GetPeakPrivateFootprintInMB() < 0) {
message_stream << " peak private: unknown";
} else {
message_stream << " peak private: "
<< mem_monitor_->GetPeakPrivateFootprintInMB() << " MB";
}
TFLITE_LOG(INFO) << message_stream.str();
}
} // namespace memory
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,56 @@
/* Copyright 2025 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_PROFILING_MEMORY_LATENCY_LOGGER_H_
#define TENSORFLOW_LITE_PROFILING_MEMORY_LATENCY_LOGGER_H_
#include <memory>
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "tensorflow/lite/profiling/memory_usage_monitor.h"
namespace tflite {
namespace profiling {
namespace memory {
// This class is used to measure the memory and latency of the surrounding code
// block. Example usage:
// MemoryLatencyLogger logger;
// logger.Start();
// Code block
// logger.Stop("Code block");
// This class is thread-unsafe.
class MemoryLatencyLogger {
public:
MemoryLatencyLogger();
// Starts the memory and latency monitoring.
void Start();
// Stops the memory and latency monitoring and logs the results.
void Stop(absl::string_view log_message);
private:
// The memory usage monitor.
std::unique_ptr<MemoryUsageMonitor> mem_monitor_;
// The start time of the memory and latency monitoring.
absl::Time start_;
};
} // namespace memory
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_MEMORY_LATENCY_LOGGER_H_
@@ -0,0 +1,103 @@
/* Copyright 2021 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/profiling/memory_usage_monitor.h"
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/synchronization/mutex.h"
#include "absl/synchronization/notification.h"
#include "absl/time/time.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/profiling/memory_info.h"
namespace tflite {
namespace profiling {
namespace memory {
MemoryUsageMonitor::MemoryUsageMonitor(int sampling_interval_ms,
std::unique_ptr<Sampler> sampler)
: sampler_(std::move(sampler)),
is_supported_(false),
sampling_interval_(absl::Milliseconds(sampling_interval_ms)) {
is_supported_ = (sampler_ != nullptr && sampler_->IsSupported());
if (!is_supported_) {
TFLITE_LOG(TFLITE_LOG_INFO,
"Getting memory usage isn't supported on this platform!\n");
return;
}
}
void MemoryUsageMonitor::Start() {
if (!is_supported_) return;
if (check_memory_thd_ != nullptr) {
TFLITE_LOG(TFLITE_LOG_INFO, "Memory monitoring has already started!\n");
return;
}
stop_signal_ = std::make_unique<absl::Notification>();
check_memory_thd_ = std::make_unique<std::thread>(([this]() {
// Note we retrieve the memory usage at the very beginning of the thread.
while (true) {
const auto mem_info = sampler_->GetMemoryUsage();
{
absl::MutexLock lock(mutex_);
int64_t current_peak_bytes = mem_info.mem_footprint_kb * 1024;
if (current_peak_bytes > peak_mem_footprint_bytes_) {
peak_mem_footprint_bytes_ = current_peak_bytes;
}
int64_t current_in_use_bytes =
static_cast<int64_t>(mem_info.in_use_allocated_bytes);
if (current_in_use_bytes > peak_in_use_mem_bytes_) {
peak_in_use_mem_bytes_ = current_in_use_bytes;
}
int64_t current_private_footprint_bytes =
mem_info.private_footprint_bytes;
if (current_private_footprint_bytes > peak_private_footprint_bytes_) {
peak_private_footprint_bytes_ = current_private_footprint_bytes;
}
}
if (stop_signal_->HasBeenNotified()) break;
sampler_->SleepFor(sampling_interval_);
}
}));
}
void MemoryUsageMonitor::Stop() {
if (!is_supported_) return;
if (check_memory_thd_ == nullptr) {
TFLITE_LOG(TFLITE_LOG_INFO,
"Memory monitoring hasn't started yet or has stopped!\n");
return;
}
StopInternal();
}
void MemoryUsageMonitor::StopInternal() {
if (check_memory_thd_ == nullptr) return;
stop_signal_->Notify();
if (check_memory_thd_ != nullptr) {
check_memory_thd_->join();
}
stop_signal_.reset(nullptr);
check_memory_thd_.reset(nullptr);
}
} // namespace memory
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,134 @@
/* Copyright 2021 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_PROFILING_MEMORY_USAGE_MONITOR_H_
#define TENSORFLOW_LITE_PROFILING_MEMORY_USAGE_MONITOR_H_
#include <cstdint>
#include <memory>
#include <thread> // NOLINT(build/c++11)
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/synchronization/notification.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tensorflow/lite/profiling/memory_info.h"
namespace tflite {
namespace profiling {
namespace memory {
// This class could help to tell the peak memory footprint of a running program.
// It achieves this by spawning a thread to check the memory usage periodically
// at a pre-defined frequency.
class MemoryUsageMonitor {
public:
// A helper class that does memory usage sampling. This allows injecting an
// external dependency for the sake of testing or providing platform-specific
// implementations.
class Sampler {
public:
virtual ~Sampler() = default;
virtual bool IsSupported() { return MemoryUsage::IsSupported(); }
virtual MemoryUsage GetMemoryUsage() {
return tflite::profiling::memory::GetMemoryUsage();
}
virtual void SleepFor(const absl::Duration& duration) {
absl::SleepFor(duration);
}
};
static constexpr int64_t kInvalidMemUsageMB = -1;
static constexpr int64_t kInvalidMemUsageBytes =
kInvalidMemUsageMB * 1024 * 1024;
explicit MemoryUsageMonitor(int sampling_interval_ms = 50)
: MemoryUsageMonitor(sampling_interval_ms, std::make_unique<Sampler>()) {}
MemoryUsageMonitor(int sampling_interval_ms,
std::unique_ptr<Sampler> sampler);
~MemoryUsageMonitor() { StopInternal(); }
void Start() ABSL_LOCKS_EXCLUDED(mutex_);
void Stop();
// For simplicity, we will return kInvalidMemUsageMB for the either following
// conditions:
// 1. getting memory usage isn't supported on the platform.
// 2. the memory usage is being monitored (i.e. we've created the
// 'check_memory_thd_'.
float GetPeakMemUsageInMB() const ABSL_LOCKS_EXCLUDED(mutex_) {
if (!is_supported_) {
return kInvalidMemUsageMB;
}
absl::MutexLock lock(mutex_);
return BytesToMegabytes(peak_mem_footprint_bytes_);
}
float GetCurrentInUseMemoryInMB() const ABSL_LOCKS_EXCLUDED(mutex_) {
int64_t in_use_mem_bytes =
sampler_->GetMemoryUsage().in_use_allocated_bytes;
if (in_use_mem_bytes < 0) {
return kInvalidMemUsageMB;
}
return BytesToMegabytes(in_use_mem_bytes);
}
float GetPeakInUseMemoryInMB() const ABSL_LOCKS_EXCLUDED(mutex_) {
if (!is_supported_) {
return kInvalidMemUsageMB;
}
absl::MutexLock lock(mutex_);
return BytesToMegabytes(peak_in_use_mem_bytes_);
}
float GetPeakPrivateFootprintInMB() const ABSL_LOCKS_EXCLUDED(mutex_) {
if (!is_supported_) {
return kInvalidMemUsageMB;
}
absl::MutexLock lock(mutex_);
return BytesToMegabytes(peak_private_footprint_bytes_);
}
MemoryUsageMonitor(MemoryUsageMonitor&) = delete;
MemoryUsageMonitor& operator=(const MemoryUsageMonitor&) = delete;
MemoryUsageMonitor(MemoryUsageMonitor&&) = delete;
MemoryUsageMonitor& operator=(const MemoryUsageMonitor&&) = delete;
private:
inline float BytesToMegabytes(int64_t bytes) const {
return bytes / 1024.0 / 1024.0;
}
void StopInternal();
mutable absl::Mutex mutex_;
std::unique_ptr<Sampler> sampler_ = nullptr;
bool is_supported_ = false;
std::unique_ptr<absl::Notification> stop_signal_ = nullptr;
absl::Duration sampling_interval_;
std::unique_ptr<std::thread> check_memory_thd_ = nullptr;
int64_t peak_mem_footprint_bytes_ ABSL_GUARDED_BY(mutex_) =
kInvalidMemUsageBytes;
int64_t peak_in_use_mem_bytes_ ABSL_GUARDED_BY(mutex_) =
kInvalidMemUsageBytes;
int64_t peak_private_footprint_bytes_ ABSL_GUARDED_BY(mutex_) =
kInvalidMemUsageBytes;
};
} // namespace memory
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_MEMORY_USAGE_MONITOR_H_
@@ -0,0 +1,192 @@
/* Copyright 2021 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/profiling/memory_usage_monitor.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include <gtest/gtest.h>
#include "absl/synchronization/notification.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "tensorflow/lite/profiling/memory_info.h"
namespace tflite {
namespace profiling {
namespace memory {
class MemoryUsageNotSupportedSampler : public MemoryUsageMonitor::Sampler {
public:
bool IsSupported() override { return false; }
};
TEST(MemoryUsageMonitor, NotSupported) {
MemoryUsageMonitor monitor1(50, std::unique_ptr<MemoryUsageMonitor::Sampler>(
new MemoryUsageNotSupportedSampler()));
EXPECT_FLOAT_EQ(MemoryUsageMonitor::kInvalidMemUsageMB,
monitor1.GetPeakMemUsageInMB());
MemoryUsageMonitor monitor2(50, nullptr);
EXPECT_FLOAT_EQ(MemoryUsageMonitor::kInvalidMemUsageMB,
monitor2.GetPeakMemUsageInMB());
}
// Just smoke tests for different call combinations.
class MemoryUsageMonitorTest : public ::testing::Test {
protected:
class FakeMemoryUsageSampler : public MemoryUsageMonitor::Sampler {
public:
explicit FakeMemoryUsageSampler(
std::atomic<int64_t>* num_sleeps,
absl::Notification* first_sample_notification)
: sleep_cnt_(num_sleeps),
first_sample_notification_(first_sample_notification) {
notification_called_.clear();
}
bool IsSupported() override { return true; }
MemoryUsage GetMemoryUsage() override {
MemoryUsage result;
result.mem_footprint_kb = 5 * (sleep_cnt_->load() + 1) * 1024;
if (!notification_called_.test_and_set()) {
first_sample_notification_->Notify();
}
return result;
}
void SleepFor(const absl::Duration& duration) override {
absl::SleepFor(duration);
sleep_cnt_->fetch_add(1);
}
private:
std::atomic<int64_t>* const sleep_cnt_ = nullptr;
absl::Notification* first_sample_notification_ = nullptr;
std::atomic_flag notification_called_;
};
void SetUp() override {
first_sample_notification_ = std::make_unique<absl::Notification>();
monitor_ = std::make_unique<MemoryUsageMonitor>(
/*sampling_interval_ms=*/50,
std::unique_ptr<MemoryUsageMonitor::Sampler>(new FakeMemoryUsageSampler(
&num_sleeps_, first_sample_notification_.get())));
}
void WaitForPeakSync(float* sync_peak = nullptr,
int64_t* sync_sleeps = nullptr) {
float peak;
int64_t sleeps;
auto condition = [&peak, &sleeps, this]() {
peak = monitor_->GetPeakMemUsageInMB();
sleeps = num_sleeps_.load();
return peak == 5.0 * (sleeps + 1);
};
// Use a timeout to avoid infinite loops.
absl::Time deadline = absl::Now() + absl::Seconds(3);
while (!condition() && absl::Now() < deadline) {
absl::SleepFor(absl::Milliseconds(10));
}
if (sync_peak) *sync_peak = peak;
if (sync_sleeps) *sync_sleeps = sleeps;
}
std::atomic<int64_t> num_sleeps_{0};
std::unique_ptr<absl::Notification> first_sample_notification_;
std::unique_ptr<MemoryUsageMonitor> monitor_ = nullptr;
};
TEST_F(MemoryUsageMonitorTest, StartAndStop) {
monitor_->Start();
first_sample_notification_->WaitForNotificationWithTimeout(absl::Seconds(1));
float peak;
int64_t sleeps;
WaitForPeakSync(&peak, &sleeps);
monitor_->Stop();
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
}
TEST_F(MemoryUsageMonitorTest, NoStartAndStop) {
monitor_->Stop();
EXPECT_FLOAT_EQ(MemoryUsageMonitor::kInvalidMemUsageMB,
monitor_->GetPeakMemUsageInMB());
}
TEST_F(MemoryUsageMonitorTest, StartAndNoStop) {
monitor_->Start();
first_sample_notification_->WaitForNotificationWithTimeout(absl::Seconds(1));
float peak;
int64_t sleeps;
WaitForPeakSync(&peak, &sleeps);
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
}
TEST_F(MemoryUsageMonitorTest, StopFirst) {
monitor_->Stop();
EXPECT_FLOAT_EQ(MemoryUsageMonitor::kInvalidMemUsageMB,
monitor_->GetPeakMemUsageInMB());
monitor_->Start();
monitor_->Stop();
EXPECT_GT(monitor_->GetPeakMemUsageInMB(), 0);
}
TEST_F(MemoryUsageMonitorTest, MultiStartAndStops) {
monitor_->Start();
monitor_->Start();
monitor_->Stop();
monitor_->Stop();
float peak;
int64_t sleeps;
WaitForPeakSync(&peak, &sleeps);
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
}
TEST_F(MemoryUsageMonitorTest, StartStopPairs) {
monitor_->Start();
first_sample_notification_->WaitForNotificationWithTimeout(absl::Seconds(1));
float peak;
int64_t sleeps;
WaitForPeakSync(&peak, &sleeps);
monitor_->Stop();
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
monitor_->Start();
// Sleep for at least for a duration that's longer than the sampling interval
// passed to 'monitor_' (i.e. 50 ms) to simulate the memory usage increase.
absl::SleepFor(absl::Milliseconds(100));
WaitForPeakSync(&peak, &sleeps);
monitor_->Stop();
EXPECT_GE(sleeps, 1);
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
}
TEST_F(MemoryUsageMonitorTest, StartReadStop) {
monitor_->Start();
// Sleep to allow the monitor to make the first sample.
first_sample_notification_->WaitForNotificationWithTimeout(absl::Seconds(1));
float peak;
int64_t sleeps;
WaitForPeakSync(&peak, &sleeps);
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
// Sleep for at least for a duration that's longer than the sampling interval
// passed to 'monitor_' (i.e. 50 ms) to simulate the memory usage increase.
absl::SleepFor(absl::Milliseconds(100));
WaitForPeakSync(&peak, &sleeps);
EXPECT_FLOAT_EQ(5.0 * (sleeps + 1), peak);
monitor_->Stop();
}
} // namespace memory
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,251 @@
/* Copyright 2024 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/profiling/model_runtime_info.h"
#include <cstdint>
#include <cstdio>
#include <fstream>
#include <ios>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "google/protobuf/repeated_field.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/optional_debug_tools.h"
#include "tensorflow/lite/profiling/proto/model_runtime_info.pb.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace profiling {
namespace {
Edge::DataType GetEdgeDataTypeFromTfLiteType(TfLiteType type) {
// LINT.IfChange(EdgeDataTypeTransform)
if (static_cast<int>(Edge::DataType_MIN) <= static_cast<int>(type) &&
static_cast<int>(type) <= static_cast<int>(Edge::DataType_MAX)) {
return static_cast<Edge::DataType>(type);
}
// LINT.ThenChange()
TFLITE_LOG(ERROR) << "Mapping TfLiteType to Edge::DataType failed: " << type;
return Edge::UNKNOWN_TYPE;
}
TfLiteStatus TfliteIntArrayToRepeatedField(
const TfLiteIntArray* array, google::protobuf::RepeatedField<int32_t>* repeated_field,
bool check_for_null = false) {
if (array == nullptr) {
return check_for_null ? kTfLiteError : kTfLiteOk;
}
repeated_field->Reserve(array->size);
for (int i = 0; i < array->size; ++i) {
repeated_field->Add(array->data[i]);
}
return kTfLiteOk;
}
TfLiteStatus TfliteTensorToEdge(const TfLiteTensor& tensor, int tensor_index,
Edge& edge_proto) {
edge_proto.set_id(tensor_index);
const std::string tensor_name =
tensor.name == nullptr ? "" : std::string(tensor.name);
edge_proto.set_name(tensor_name);
edge_proto.set_data_type(GetEdgeDataTypeFromTfLiteType(tensor.type));
edge_proto.set_size(tensor.bytes);
edge_proto.set_layout_type(Edge::UNKNOWN);
edge_proto.set_allocation_type(AllocTypeName(tensor.allocation_type));
const auto status =
TfliteIntArrayToRepeatedField(tensor.dims, edge_proto.mutable_shape());
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to convert tensor.dims to RepeatedField as it "
"is null for tensor "
<< tensor_name << " with index " << tensor_index;
return status;
}
return kTfLiteOk;
}
// Converts a TfLiteNode to a Node proto.
//
// If the node is a delegate node, the type is set to "Delegate/{CustomName}"
// to keep this in sync with the types used in op-profiling.
// If is_node_delegated is true, the node is a TfLite node that has been
// delegated to another node provided by delegated_to_node_id. If
// is_node_delegated is false, delegated_to_node_id is ignored.
TfLiteStatus TfliteNodeToNode(const TfLiteNode& node,
const TfLiteRegistration& reg, int node_index,
bool is_node_delegated,
int32_t delegated_to_node_id, Node& node_proto) {
node_proto.set_id(node_index);
if (reg.custom_name != nullptr) {
node_proto.set_name(reg.custom_name);
// If this node is delegated, the type is saved as "Delegate/{CustomName}"
// to keep this in sync with the types used in op-profiling.
node_proto.set_type((is_node_delegated ? "Delegate/" : "") +
std::string(reg.custom_name));
} else {
// If this node is not a custom op, the name is set to the builtin op name.
node_proto.set_name(EnumNamesBuiltinOperator()[reg.builtin_code]);
node_proto.set_type(std::to_string(reg.builtin_code));
}
auto status = TfliteIntArrayToRepeatedField(
node.inputs, node_proto.mutable_inputs(), /*check_for_null=*/true);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to convert node.inputs to RepeatedField as it "
"is null for node "
<< node_proto.name() << " with index " << node_index;
return status;
}
status = TfliteIntArrayToRepeatedField(
node.outputs, node_proto.mutable_outputs(), /*check_for_null=*/true);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR)
<< "Failed to convert node.outputs to RepeatedField as it "
"is null for node "
<< node_proto.name() << " with index " << node_index;
return status;
}
status = TfliteIntArrayToRepeatedField(node.intermediates,
node_proto.mutable_intermediates());
if (status != kTfLiteOk) {
return status;
}
status = TfliteIntArrayToRepeatedField(node.temporaries,
node_proto.mutable_temporaries());
if (status != kTfLiteOk) {
return status;
}
if (is_node_delegated) {
// This node is delegated to another node.
node_proto.set_delegated_to_node_id(delegated_to_node_id);
} else if (node.delegate != nullptr) {
// This node is a delegate node that replaces other TfLite nodes.
auto delegate_node_details = node_proto.mutable_delegate_node_details();
delegate_node_details->set_delegate_name(reg.custom_name);
auto* delegate_params =
static_cast<TfLiteDelegateParams*>(node.builtin_data);
status = TfliteIntArrayToRepeatedField(
delegate_params->nodes_to_replace,
delegate_node_details->mutable_tflite_node_ids_replaced(),
/*check_for_null=*/true);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to convert delegate_params->nodes_to_replace"
" to RepeatedField as it is null for node "
<< node_proto.name() << " with index " << node_index;
return status;
}
}
return kTfLiteOk;
}
} // namespace
TfLiteStatus GenerateModelRuntimeInfo(
const tflite::Interpreter& interpreter,
ModelRuntimeDetails& model_runtime_details) {
const size_t num_subgraphs = interpreter.subgraphs_size();
for (int i = 0; i < num_subgraphs; ++i) {
RuntimeSubgraph* runtime_subgraph = model_runtime_details.add_subgraphs();
runtime_subgraph->set_subgraph_id(i);
runtime_subgraph->set_subgraph_type(RuntimeSubgraph::TFLITE_SUBGRAPH);
runtime_subgraph->set_name(interpreter.subgraph(i)->GetName());
const tflite::Subgraph& subgraph = *(interpreter.subgraph(i));
// Capturing information of all the tensors in this subgraph.
for (size_t tensor_index = 0; tensor_index < subgraph.tensors_size();
tensor_index++) {
const TfLiteTensor* tensor =
subgraph.tensor(static_cast<int>(tensor_index));
Edge* edge = runtime_subgraph->add_edges();
auto status = TfliteTensorToEdge(*tensor, tensor_index, *edge);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to convert tensor to edge, tensor index: "
<< tensor_index;
return status;
}
}
// Iterating over all the nodes in this subgraph.
const SubgraphDelegationMetadata delegation_metadata =
GetNodeDelegationMetadata(subgraph);
for (size_t node_index = 0; node_index < subgraph.nodes_size();
node_index++) {
const std::pair<TfLiteNode, TfLiteRegistration>* node_and_reg =
subgraph.node_and_registration(static_cast<int>(node_index));
const TfLiteNode& node = node_and_reg->first;
const TfLiteRegistration& reg = node_and_reg->second;
Node* runtime_node = runtime_subgraph->add_nodes();
const bool is_node_delegated =
node.delegate == nullptr &&
delegation_metadata.is_node_delegated[node_index];
TfLiteStatus status = TfliteNodeToNode(
node, reg, node_index, is_node_delegated,
is_node_delegated ? delegation_metadata.replaced_by_node[node_index]
: -1,
*runtime_node);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to convert node to runtime node, node "
"index: "
<< node_index;
return status;
}
}
// Save the execution plan to runtime subgraph.
runtime_subgraph->mutable_execution_plan()->Add(
subgraph.execution_plan().begin(), subgraph.execution_plan().end());
}
return kTfLiteOk;
}
TfLiteStatus GenerateModelRuntimeInfo(const tflite::Interpreter& interpreter,
absl::string_view output_file_path) {
ModelRuntimeDetails model_runtime_details;
auto status = GenerateModelRuntimeInfo(interpreter, model_runtime_details);
if (status != kTfLiteOk) {
TFLITE_LOG(ERROR) << "Failed to generate model runtime info: " << status;
return status;
}
std::ofstream ofs(std::string(output_file_path),
std::ios::out | std::ios::binary);
if (ofs.good()) {
model_runtime_details.SerializeToOstream(&ofs);
ofs.close();
} else {
TFLITE_LOG(ERROR) << "Failed to open file: " << output_file_path;
TFLITE_LOG(INFO) << model_runtime_details.DebugString();
}
return kTfLiteOk;
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,38 @@
/* Copyright 2024 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_PROFILING_MODEL_RUNTIME_INFO_H_
#define TENSORFLOW_LITE_PROFILING_MODEL_RUNTIME_INFO_H_
#include "absl/strings/string_view.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/profiling/proto/model_runtime_info.pb.h"
namespace tflite {
namespace profiling {
// Generates a ModelRuntimeInfo proto for the given interpreter and writes it to
// the given output file path.
TfLiteStatus GenerateModelRuntimeInfo(const Interpreter &interpreter,
absl::string_view output_file_path);
// Generates a ModelRuntimeInfo proto for the given interpreter and writes it to
// the given model_runtime_details proto.
TfLiteStatus GenerateModelRuntimeInfo(
const Interpreter &interpreter, ModelRuntimeDetails &model_runtime_details);
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_MODEL_RUNTIME_INFO_H_
@@ -0,0 +1,402 @@
/* Copyright 2024 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/profiling/model_runtime_info.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <ios>
#include <iostream>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/profiling/buffered_profiler.h"
#include "tensorflow/lite/profiling/proto/model_runtime_info.pb.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace profiling {
// A model that runs a pad op followed by a conv_2d op.
// XNNPACK will fuse the pad op into the conv op while TFLite on CPU will not.
class PadAndConv2DModel : public MultiOpModel {
public:
explicit PadAndConv2DModel(Interpreter::TfLiteDelegatePtr delegate = {
nullptr, [](TfLiteDelegate*) {}}) {
input_ = AddInput({TensorType_FLOAT32, {1, 3, 3, 1}});
int pad_out = AddInnerTensor<float>({TensorType_FLOAT32, {1, 5, 5, 1}});
output_ = AddOutput({TensorType_FLOAT32, {1, 5, 5, 1}});
int padding_in_ =
AddConstInput({TensorType_INT32, {4, 2}}, {0, 0, 1, 1, 1, 1, 0, 0});
int conv_filter_ =
AddConstInput({TensorType_FLOAT32, {1, 2, 2, 1}}, {0, 1, 1, 0});
int conv_bias_ = AddConstInput({TensorType_FLOAT32, {1}}, {3});
AddBuiltinOp(tflite::BuiltinOperator_PAD, tflite::BuiltinOptions_PadOptions,
CreatePadOptions(builder_).Union(), {input_, padding_in_},
{pad_out});
AddBuiltinOp(
tflite::BuiltinOperator_CONV_2D, tflite::BuiltinOptions_Conv2DOptions,
CreateConv2DOptions(builder_, tflite::Padding_SAME, 1, 1).Union(),
{pad_out, conv_filter_, conv_bias_}, {output_});
bool apply_delegate = delegate != nullptr;
SetDelegate(std::move(delegate));
BuildInterpreter({GetShape(input_)}, /*num_threads=*/-1,
/*allow_fp32_relax_to_fp16=*/false,
/*apply_delegate=*/apply_delegate,
/*allocate_and_delegate=*/false);
SetSubgraphNames();
}
void SetSubgraphNames() {
for (int i = 0; i < interpreter_->subgraphs_size(); ++i) {
interpreter_->subgraph(i)->SetName(
std::string("subgraph_" + std::to_string(i)).c_str());
}
}
int input() const { return input_; }
int output() const { return output_; }
void SetProfiler(Profiler* profiler) { interpreter_->SetProfiler(profiler); }
Interpreter* interpreter() const { return interpreter_.get(); }
void Initialize(Profiler* profiler) {
if (profiler != nullptr) {
SetProfiler(profiler);
}
AllocateAndDelegate(true);
}
void ResetProfilerAndInvoke(profiling::BufferedProfiler* profiler) {
profiler->Reset();
profiler->StartProfiling();
PopulateTensor(input(),
{1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f});
ASSERT_EQ(kTfLiteOk, Invoke());
profiler->StopProfiling();
}
private:
int input_;
int output_;
};
bool AreRepeatedIntFieldsEqual(const google::protobuf::RepeatedField<int32_t>& field_1,
const google::protobuf::RepeatedField<int32_t>& field_2) {
return std::equal(field_1.begin(), field_1.end(), field_2.begin(),
field_2.end());
}
bool AreEdgesEqual(const Edge& edge_1, const Edge& edge_2) {
auto proto_to_tuple = [](const Edge& edge) {
return std::make_tuple(edge.id(), edge.name(), edge.data_type(),
edge.size(), edge.layout_type(),
edge.allocation_type());
};
return proto_to_tuple(edge_1) == proto_to_tuple(edge_2) &&
AreRepeatedIntFieldsEqual(edge_1.shape(), edge_2.shape());
}
bool AreNodesEqual(const Node& node_1, const Node& node_2) {
auto proto_to_tuple = [](const Node& node) {
return std::make_tuple(node.id(), node.name(), node.type());
};
return proto_to_tuple(node_1) == proto_to_tuple(node_2) &&
AreRepeatedIntFieldsEqual(node_1.inputs(), node_2.inputs()) &&
AreRepeatedIntFieldsEqual(node_1.outputs(), node_2.outputs()) &&
AreRepeatedIntFieldsEqual(node_1.intermediates(),
node_2.intermediates()) &&
AreRepeatedIntFieldsEqual(node_1.temporaries(), node_2.temporaries());
}
bool AreRuntimeSubgraphsEqual(const RuntimeSubgraph& subgraph_1,
const RuntimeSubgraph& subgraph_2) {
auto proto_to_tuple = [](const RuntimeSubgraph& subgraph) {
return std::make_tuple(subgraph.subgraph_id(), subgraph.subgraph_type(),
subgraph.execution_plan().size(),
subgraph.nodes_size(), subgraph.edges_size(),
subgraph.name());
};
if (proto_to_tuple(subgraph_1) == proto_to_tuple(subgraph_2) &&
AreRepeatedIntFieldsEqual(subgraph_1.execution_plan(),
subgraph_2.execution_plan())) {
for (size_t i = 0; i < subgraph_1.nodes_size(); ++i) {
if (!AreNodesEqual(subgraph_1.nodes(i), subgraph_2.nodes(i))) {
return false;
}
}
for (size_t i = 0; i < subgraph_1.edges_size(); ++i) {
if (!AreEdgesEqual(subgraph_1.edges(i), subgraph_2.edges(i))) {
return false;
}
}
return true;
}
return false;
}
bool AreModelRuntimeDetailsEqual(const ModelRuntimeDetails& model_details_1,
const ModelRuntimeDetails& model_details_2) {
auto proto_to_tuple = [](const ModelRuntimeDetails& model_details) {
return std::make_tuple(model_details.model_name(),
model_details.subgraphs_size());
};
if (proto_to_tuple(model_details_1) == proto_to_tuple(model_details_2)) {
for (size_t i = 0; i < model_details_1.subgraphs_size(); ++i) {
if (!AreRuntimeSubgraphsEqual(model_details_1.subgraphs(i),
model_details_2.subgraphs(i))) {
return false;
}
}
return true;
}
return false;
}
ModelRuntimeDetails CreateExpectedModelRuntimeDetails(
bool is_xnnpack_delegate) {
ModelRuntimeDetails expected_model_runtime_details;
RuntimeSubgraph* subgraph = expected_model_runtime_details.add_subgraphs();
subgraph->set_subgraph_id(0);
subgraph->set_name("subgraph_0");
subgraph->set_subgraph_type(RuntimeSubgraph::TFLITE_SUBGRAPH);
if (is_xnnpack_delegate) {
subgraph->add_execution_plan(2);
} else {
subgraph->add_execution_plan(0);
subgraph->add_execution_plan(1);
}
Node* node = subgraph->add_nodes();
node->set_id(0);
node->set_name("PAD");
node->set_type("34");
node->add_inputs(0);
node->add_inputs(3);
node->add_outputs(1);
Node* node_2 = subgraph->add_nodes();
node_2->set_id(1);
node_2->set_name("CONV_2D");
node_2->set_type("3");
node_2->add_inputs(1);
node_2->add_inputs(4);
node_2->add_inputs(5);
node_2->add_outputs(2);
if (!is_xnnpack_delegate) {
node_2->add_temporaries(6);
}
if (is_xnnpack_delegate) {
node->set_delegated_to_node_id(2);
node_2->set_delegated_to_node_id(2);
Node* node_3 = subgraph->add_nodes();
node_3->set_id(2);
node_3->set_name("TfLiteXNNPackDelegate");
node_3->set_type("TfLiteXNNPackDelegate");
node_3->add_inputs(0);
node_3->add_inputs(3);
node_3->add_inputs(4);
node_3->add_inputs(5);
node_3->add_outputs(2);
DelegateNodeDetails* delegate_node_details =
node_3->mutable_delegate_node_details();
delegate_node_details->set_delegate_name("TfLiteXNNPackDelegate");
delegate_node_details->add_tflite_node_ids_replaced(0);
delegate_node_details->add_tflite_node_ids_replaced(1);
}
Edge* edge = subgraph->add_edges();
edge->set_id(0);
edge->set_name("");
edge->set_data_type(Edge::FLOAT32);
edge->set_size(36);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(1);
edge->add_shape(3);
edge->add_shape(3);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteArenaRw");
edge = subgraph->add_edges();
edge->set_id(1);
edge->set_name("");
edge->set_data_type(Edge::FLOAT32);
edge->set_size(100);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(1);
edge->add_shape(5);
edge->add_shape(5);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteArenaRw");
edge = subgraph->add_edges();
edge->set_id(2);
edge->set_name("");
edge->set_data_type(Edge::FLOAT32);
edge->set_size(100);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(1);
edge->add_shape(5);
edge->add_shape(5);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteArenaRw");
edge = subgraph->add_edges();
edge->set_id(3);
edge->set_name("");
edge->set_data_type(Edge::INT32);
edge->set_size(32);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(4);
edge->add_shape(2);
edge->set_allocation_type("kTfLiteMmapRo");
edge = subgraph->add_edges();
edge->set_id(4);
edge->set_name("");
edge->set_data_type(Edge::FLOAT32);
edge->set_size(16);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(1);
edge->add_shape(2);
edge->add_shape(2);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteMmapRo");
edge = subgraph->add_edges();
edge->set_id(5);
edge->set_name("");
edge->set_data_type(Edge::FLOAT32);
edge->set_size(4);
edge->set_layout_type(Edge::UNKNOWN);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteMmapRo");
if (!is_xnnpack_delegate) {
edge = subgraph->add_edges();
edge->set_id(6);
edge->set_data_type(Edge::FLOAT32);
edge->set_layout_type(Edge::UNKNOWN);
edge->set_allocation_type("kTfLiteArenaRwPersistent");
#if (__ANDROID__ && (__aarch64__ || __arm__ || __aarch32__)) || \
(defined(__APPLE__) && TARGET_OS_IPHONE)
// On Android Arm and iOS builds, the Conv2D op uses im2col.
edge->set_name("");
edge->set_size(is_xnnpack_delegate ? 0 : 400);
edge->add_shape(1);
edge->add_shape(5);
edge->add_shape(5);
edge->add_shape(4);
edge->set_allocation_type("kTfLiteArenaRw");
#else
edge->set_name("Conv_hwcn_weights");
edge->set_size(is_xnnpack_delegate ? 0 : 16);
edge->add_shape(4);
edge->add_shape(1);
edge->set_allocation_type("kTfLiteArenaRwPersistent");
#endif
}
return expected_model_runtime_details;
}
TEST(MODEL_RUNTIME_INFO_TEST, PadAndConv2DNoDelegate) {
auto profiler = std::make_unique<profiling::BufferedProfiler>(1024, false);
PadAndConv2DModel model;
model.Initialize(profiler.get());
model.ResetProfilerAndInvoke(profiler.get());
#ifdef __ANDROID__
std::string file_name = "/data/local/tmp/test_file.textproto";
#else
std::string file_name = "/tmp/test_file.textproto";
#endif
auto status = GenerateModelRuntimeInfo(*model.interpreter(), file_name);
ASSERT_TRUE(status == kTfLiteOk);
ModelRuntimeDetails model_runtime_details;
std::ifstream file(file_name, std::ios::binary);
ASSERT_TRUE(file.good());
model_runtime_details.ParseFromIstream(&file);
file.close();
ModelRuntimeDetails expected_model_runtime_details =
CreateExpectedModelRuntimeDetails(/*is_xnnpack_delegate=*/false);
ASSERT_TRUE(AreModelRuntimeDetailsEqual(model_runtime_details,
expected_model_runtime_details));
}
TEST(MODEL_RUNTIME_INFO_TEST, PadAndConv2DWithXnnpackDelegate) {
auto profiler = std::make_unique<profiling::BufferedProfiler>(1024, false);
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
PadAndConv2DModel xnnpack_model(std::move(xnnpack_delegate));
xnnpack_model.Initialize(profiler.get());
xnnpack_model.ResetProfilerAndInvoke(profiler.get());
#ifdef __ANDROID__
std::string file_name = "/data/local/tmp/test_file.textproto";
#else
std::string file_name = "/tmp/test_file.textproto";
#endif
auto status =
GenerateModelRuntimeInfo(*xnnpack_model.interpreter(), file_name);
ASSERT_TRUE(status == kTfLiteOk);
ModelRuntimeDetails model_runtime_details;
std::ifstream file(file_name, std::ios::binary);
ASSERT_TRUE(file.good());
model_runtime_details.ParseFromIstream(&file);
file.close();
ModelRuntimeDetails expected_model_runtime_details =
CreateExpectedModelRuntimeDetails(/*is_xnnpack_delegate=*/true);
ASSERT_TRUE(AreModelRuntimeDetailsEqual(model_runtime_details,
expected_model_runtime_details))
<< "model_runtime_details:\n"
<< model_runtime_details.DebugString()
<< "expected_model_runtime_details:\n"
<< expected_model_runtime_details.DebugString();
}
} // namespace profiling
} // namespace tflite
+46
View File
@@ -0,0 +1,46 @@
/* 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_PROFILING_NOOP_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_NOOP_PROFILER_H_
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/profile_buffer.h"
namespace tflite {
namespace profiling {
// A noop version of profiler when profiling is disabled.
class NoopProfiler : public tflite::Profiler {
public:
NoopProfiler() {}
explicit NoopProfiler(int max_profiling_buffer_entries) {}
uint32_t BeginEvent(const char*, EventType, int64_t, int64_t) override {
return 0;
}
void EndEvent(uint32_t) override {}
void StartProfiling() {}
void StopProfiling() {}
void Reset() {}
std::vector<const ProfileEvent*> GetProfileEvents() { return {}; }
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_NOOP_PROFILER_H_
@@ -0,0 +1,49 @@
/* Copyright 2020 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/profiling/platform_profiler.h"
#include <memory>
#include "tensorflow/lite/core/api/profiler.h"
#if defined(__ANDROID__)
#include "tensorflow/lite/profiling/atrace_profiler.h"
#elif defined(__APPLE__)
#include "TargetConditionals.h"
#if TARGET_OS_IOS
#define SIGNPOST_PLATFORM_PROFILER
#include "tensorflow/lite/profiling/signpost_profiler.h"
#endif
#elif defined(ENABLE_TFLITE_PERFETTO_PROFILER)
#include "tensorflow/lite/experimental/perfetto_profiling/perfetto_profiler.h"
#endif
namespace tflite {
namespace profiling {
std::unique_ptr<tflite::Profiler> MaybeCreatePlatformProfiler() {
#if defined(__ANDROID__)
return MaybeCreateATraceProfiler();
#elif defined(SIGNPOST_PLATFORM_PROFILER)
return MaybeCreateSignpostProfiler();
#elif defined(ENABLE_TFLITE_PERFETTO_PROFILER)
return std::make_unique<tflite::profiling::PerfettoProfiler>();
#else
return nullptr;
#endif
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,30 @@
/* Copyright 2020 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_PROFILING_PLATFORM_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_PLATFORM_PROFILER_H_
#include <memory>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
std::unique_ptr<tflite::Profiler> MaybeCreatePlatformProfiler();
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_PLATFORM_PROFILER_H_
+134
View File
@@ -0,0 +1,134 @@
/* 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/profiling/profile_buffer.h"
#include <utility>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/profiling/time.h"
namespace tflite {
namespace profiling {
uint32_t ProfileBuffer::BeginEvent(const char* tag,
ProfileEvent::EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) {
if (!enabled_) {
return kInvalidEventHandle;
}
uint64_t timestamp = time::NowMicros();
const auto next_index = GetNextEntryIndex();
if (next_index.second) {
return next_index.first;
}
const int index = next_index.first;
event_buffer_[index].tag = tag;
event_buffer_[index].event_type = event_type;
event_buffer_[index].event_metadata = event_metadata1;
event_buffer_[index].extra_event_metadata = event_metadata2;
event_buffer_[index].begin_timestamp_us = timestamp;
event_buffer_[index].elapsed_time = 0;
if (event_type != Profiler::EventType::OPERATOR_INVOKE_EVENT) {
event_buffer_[index].begin_mem_usage = memory::GetMemoryUsage();
}
current_index_++;
return index;
}
void ProfileBuffer::EndEvent(uint32_t event_handle,
const int64_t* event_metadata1,
const int64_t* event_metadata2) {
if (!enabled_ || event_handle == kInvalidEventHandle ||
event_handle > current_index_) {
return;
}
const uint32_t max_size = event_buffer_.size();
if (current_index_ > (max_size + event_handle)) {
// Ignore, buffer has already overflowed.
return;
}
int event_index = event_handle % max_size;
event_buffer_[event_index].elapsed_time =
time::NowMicros() - event_buffer_[event_index].begin_timestamp_us;
if (event_buffer_[event_index].event_type !=
Profiler::EventType::OPERATOR_INVOKE_EVENT) {
event_buffer_[event_index].end_mem_usage = memory::GetMemoryUsage();
}
if (event_metadata1) {
event_buffer_[event_index].event_metadata = *event_metadata1;
}
if (event_metadata2) {
event_buffer_[event_index].extra_event_metadata = *event_metadata2;
}
}
const struct ProfileEvent* ProfileBuffer::At(size_t index) const {
size_t size = Size();
if (index >= size) {
return nullptr;
}
const uint32_t max_size = event_buffer_.size();
uint32_t start =
(current_index_ > max_size) ? current_index_ % max_size : max_size;
index = (index + start) % max_size;
return &event_buffer_[index];
}
void ProfileBuffer::AddEvent(const char* tag,
ProfileEvent::EventType event_type,
uint64_t elapsed_time, int64_t event_metadata1,
int64_t event_metadata2) {
if (!enabled_) {
return;
}
const auto next_index = GetNextEntryIndex();
if (next_index.second) {
return;
}
const int index = next_index.first;
event_buffer_[index].tag = tag;
event_buffer_[index].event_type = event_type;
event_buffer_[index].event_metadata = event_metadata1;
event_buffer_[index].extra_event_metadata = event_metadata2;
event_buffer_[index].begin_timestamp_us = 0;
event_buffer_[index].elapsed_time = elapsed_time;
current_index_++;
}
std::pair<int, bool> ProfileBuffer::GetNextEntryIndex() {
int index = current_index_ % event_buffer_.size();
if (current_index_ == 0 || index != 0) {
return std::make_pair(index, false);
}
// Current buffer is full
if (!allow_dynamic_expansion_) {
TFLITE_LOG_PROD_ONCE(TFLITE_LOG_INFO,
"Warning: Dropping ProfileBuffer event.");
return std::make_pair(current_index_, true);
} else {
TFLITE_LOG_PROD_ONCE(TFLITE_LOG_INFO,
"Warning: Doubling internal profiling buffer.");
event_buffer_.resize(current_index_ * 2);
return std::make_pair(current_index_, false);
}
}
} // namespace profiling
} // namespace tflite
+127
View File
@@ -0,0 +1,127 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_PROFILING_PROFILE_BUFFER_H_
#define TENSORFLOW_LITE_PROFILING_PROFILE_BUFFER_H_
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/profiling/time.h"
namespace tflite {
namespace profiling {
constexpr uint32_t kInvalidEventHandle = static_cast<uint32_t>(~0) - 1;
// A profiling event.
struct ProfileEvent {
// Describes the type of event.
// The event_metadata field may contain additional data for interpreting
// the event.
using EventType = tflite::Profiler::EventType;
// Label of the event. This usually describes the event.
std::string tag;
// Timestamp in microseconds when the event began.
uint64_t begin_timestamp_us;
// Event processing time in microseconds.
uint64_t elapsed_time;
// The memory usage when the event begins.
memory::MemoryUsage begin_mem_usage;
// The memory usage when the event ends.
memory::MemoryUsage end_mem_usage;
// The field containing the type of event. This must be one of the event types
// in EventType.
EventType event_type;
// Meta data associated w/ the event.
int64_t event_metadata;
// Note: if this is an OPERATOR_INVOKE_EVENT, 'extra_event_metadata' will
// represent the index of the subgraph that this event comes from.
int64_t extra_event_metadata;
};
// A buffer of profile events. In general, the buffer works like a ring buffer.
// However, when 'allow_dynamic_expansion' is set, a unlimitted number of buffer
// entries is allowed and more profiling overhead could occur.
// This class is *not thread safe*.
class ProfileBuffer {
public:
ProfileBuffer(uint32_t max_num_entries, bool enabled,
bool allow_dynamic_expansion = false)
: enabled_(enabled),
current_index_(0),
event_buffer_(max_num_entries),
allow_dynamic_expansion_(allow_dynamic_expansion) {}
// Adds an event to the buffer with begin timestamp set to the current
// timestamp. Returns a handle to event that can be used to call EndEvent. If
// buffer is disabled this has no affect.
// The tag of the event should remain valid till the buffer is valid.
uint32_t BeginEvent(const char* tag, ProfileEvent::EventType event_type,
int64_t event_metadata1, int64_t event_metadata2);
// Sets the enabled state of buffer to |enabled|
void SetEnabled(bool enabled) { enabled_ = enabled; }
// Sets the end timestamp for event for the handle to current time.
// If the buffer is disabled or previous event has been overwritten this
// operation has not effect.
void EndEvent(uint32_t event_handle, const int64_t* event_metadata1 = nullptr,
const int64_t* event_metadata2 = nullptr);
void AddEvent(const char* tag, ProfileEvent::EventType event_type,
uint64_t elapsed_time, int64_t event_metadata1,
int64_t event_metadata2);
// Returns the size of the buffer.
size_t Size() const {
return (current_index_ >= event_buffer_.size()) ? event_buffer_.size()
: current_index_;
}
// Resets the buffer.
void Reset() {
enabled_ = false;
current_index_ = 0;
}
// Returns the profile event at the given index. If the index is invalid a
// nullptr is returned. The return event may get overwritten if more events
// are added to buffer.
const struct ProfileEvent* At(size_t index) const;
private:
// Returns a pair of values. The 1st element refers to the next buffer id,
// the 2nd element refers to whether the buffer reaches its allowed capacity.
std::pair<int, bool> GetNextEntryIndex();
bool enabled_;
uint32_t current_index_;
std::vector<ProfileEvent> event_buffer_;
const bool allow_dynamic_expansion_;
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_PROFILE_BUFFER_H_
@@ -0,0 +1,142 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/profile_buffer.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include <vector>
#include <gtest/gtest.h>
namespace tflite {
namespace profiling {
namespace {
std::vector<const ProfileEvent*> GetProfileEvents(const ProfileBuffer& buffer) {
std::vector<const ProfileEvent*> events;
for (size_t i = 0; i < buffer.Size(); i++) {
events.push_back(buffer.At(i));
}
return events;
}
TEST(ProfileBufferTest, Empty) {
ProfileBuffer buffer(/*max_size*/ 0, /*enabled*/ true);
EXPECT_EQ(0, buffer.Size());
}
TEST(ProfileBufferTest, AddEvent) {
ProfileBuffer buffer(/*max_size*/ 10, /*enabled*/ true);
EXPECT_EQ(0, buffer.Size());
auto event_handle =
buffer.BeginEvent("hello", ProfileEvent::EventType::DEFAULT,
/*event_metadata1*/ 42, /*event_metadata2*/ 0);
EXPECT_GE(event_handle, 0);
EXPECT_EQ(1, buffer.Size());
auto event = GetProfileEvents(buffer)[0];
EXPECT_EQ(event->tag, "hello");
EXPECT_GT(event->begin_timestamp_us, 0);
EXPECT_EQ(event->event_type, ProfileEvent::EventType::DEFAULT);
EXPECT_EQ(event->event_metadata, 42);
buffer.EndEvent(event_handle);
EXPECT_EQ(1, buffer.Size());
EXPECT_GE(event->elapsed_time, 0);
}
TEST(ProfileBufferTest, EndEventWithMetadata) {
ProfileBuffer buffer(/*max_size*/ 10, /*enabled*/ true);
EXPECT_EQ(0, buffer.Size());
auto event_handle =
buffer.BeginEvent("hello", ProfileEvent::EventType::DEFAULT,
/*event_metadata1*/ 42, /*event_metadata2*/ 0);
const int64_t kEventMetadata1 = 18;
const int64_t kEventMetadata2 = 36;
buffer.EndEvent(event_handle, &kEventMetadata1, &kEventMetadata2);
EXPECT_GE(event_handle, 0);
EXPECT_EQ(1, buffer.Size());
auto event = GetProfileEvents(buffer)[0];
EXPECT_EQ(event->tag, "hello");
EXPECT_GT(event->begin_timestamp_us, 0);
EXPECT_EQ(event->event_type, ProfileEvent::EventType::DEFAULT);
EXPECT_EQ(event->event_metadata, kEventMetadata1);
EXPECT_EQ(event->extra_event_metadata, kEventMetadata2);
EXPECT_EQ(1, buffer.Size());
EXPECT_GE(event->elapsed_time, 0);
}
TEST(ProfileBufferTest, OverFlow) {
const int max_size = 4;
ProfileBuffer buffer{max_size, true};
std::vector<std::string> eventNames = {"first", "second", "third", "fourth"};
for (int i = 0; i < 2 * max_size; i++) {
buffer.BeginEvent(eventNames[i % 4].c_str(),
ProfileEvent::EventType::DEFAULT, i, 0);
size_t expected_size = std::min(i + 1, max_size);
EXPECT_EQ(expected_size, buffer.Size());
}
EXPECT_EQ(max_size, buffer.Size());
for (size_t j = 0; j < buffer.Size(); ++j) {
auto event = buffer.At(j);
EXPECT_EQ(eventNames[j % 4], event->tag);
EXPECT_EQ(ProfileEvent::EventType::DEFAULT, event->event_type);
EXPECT_EQ(j, event->event_metadata);
}
}
TEST(ProfileBufferTest, DynamicIncrease) {
const int max_initial_size = 4;
ProfileBuffer buffer{max_initial_size, true,
true /*allow_dynamic_buffer_increase*/};
std::vector<std::string> eventNames = {"first", "second", "third", "fourth"};
for (int i = 0; i < 2 * max_initial_size; i++) {
buffer.BeginEvent(eventNames[i % 4].c_str(),
ProfileEvent::EventType::DEFAULT, i, 0);
const size_t expected_size = i + 1;
EXPECT_EQ(expected_size, buffer.Size());
}
EXPECT_EQ(2 * max_initial_size, buffer.Size());
for (size_t j = 0; j < buffer.Size(); ++j) {
auto event = buffer.At(j);
EXPECT_EQ(eventNames[j % 4], event->tag);
EXPECT_EQ(ProfileEvent::EventType::DEFAULT, event->event_type);
EXPECT_EQ(j, event->event_metadata);
}
}
TEST(ProfileBufferTest, Enable) {
ProfileBuffer buffer(/*max_size*/ 10, /*enabled*/ false);
EXPECT_EQ(0, buffer.Size());
auto event_handle =
buffer.BeginEvent("hello", ProfileEvent::EventType::DEFAULT,
/*event_metadata1*/ 42, /*event_metadata2*/ 0);
EXPECT_EQ(kInvalidEventHandle, event_handle);
EXPECT_EQ(0, buffer.Size());
buffer.SetEnabled(true);
event_handle =
buffer.BeginEvent("hello", ProfileEvent::EventType::DEFAULT,
/*event_metadata1*/ 42, /*event_metadata2*/ 0);
EXPECT_GE(event_handle, 0);
EXPECT_EQ(1, buffer.Size());
}
} // namespace
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,225 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/profile_summarizer.h"
#include <memory>
#include <sstream>
#include <string>
#include "tensorflow/core/util/stats_calculator.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/profiling/profile_buffer.h"
#include "tensorflow/lite/profiling/profile_summary_formatter.h"
namespace tflite {
namespace profiling {
namespace {
struct OperatorDetails {
uint32_t subgraph_index;
uint32_t node_index;
std::string op_description;
std::vector<std::string> inputs;
std::vector<std::string> outputs;
};
std::string GetTensorName(const tflite::Interpreter& interpreter,
int tensor_index) {
const auto tensor = interpreter.tensor(tensor_index);
if (tensor == nullptr || tensor->name == nullptr) {
return "Unknown";
}
return tensor->name;
}
std::vector<std::string> GetTensorNames(const tflite::Interpreter& interpreter,
const TfLiteIntArray* tensor_indices) {
std::vector<std::string> tensors;
tensors.reserve(tensor_indices->size);
for (int i = 0; i < tensor_indices->size; i++) {
tensors.push_back(GetTensorName(interpreter, tensor_indices->data[i]));
}
return tensors;
}
std::string ToString(const std::vector<std::string>& str_vector) {
std::stringstream stream;
stream << "[";
bool first = true;
for (const auto& s : str_vector) {
if (!first) {
stream << ", ";
} else {
first = false;
}
stream << s;
}
stream << "]";
return stream.str();
}
OperatorDetails GetOperatorDetails(const tflite::Interpreter& interpreter,
uint32_t subgraph_index,
uint32_t node_index) {
auto subgraph =
const_cast<tflite::Interpreter&>(interpreter).subgraph(subgraph_index);
auto node_reg = subgraph->node_and_registration(node_index);
auto inputs = node_reg->first.inputs;
auto outputs = node_reg->first.outputs;
const char* profiling_string =
interpreter.OpProfilingString(node_reg->second, &node_reg->first);
OperatorDetails details;
if (profiling_string) {
details.op_description = std::string(profiling_string);
}
details.inputs = GetTensorNames(interpreter, inputs);
details.outputs = GetTensorNames(interpreter, outputs);
return details;
}
} // namespace
ProfileSummarizer::ProfileSummarizer(
std::shared_ptr<ProfileSummaryFormatter> summary_formatter)
: summary_formatter_(summary_formatter) {
// Create stats calculator for the primary graph.
stats_calculator_map_[0] = std::make_unique<tensorflow::StatsCalculator>(
summary_formatter_->GetStatSummarizerOptions());
// Create stats calculator for the delegation op.
delegate_stats_calculator_ = std::make_unique<tensorflow::StatsCalculator>(
summary_formatter_->GetStatSummarizerOptions());
}
void ProfileSummarizer::ProcessProfiles(
const std::vector<const ProfileEvent*>& profile_stats,
const tflite::Interpreter& interpreter) {
if (profile_stats.empty()) return;
int node_num = 0;
// Total time will be accumulated per subgraph.
std::map<uint32_t, int64_t> total_us_per_subgraph_map;
int64_t delegate_internal_total_us = 0;
for (auto event : profile_stats) {
const auto subgraph_index = event->extra_event_metadata;
auto stats_calculator = GetStatsCalculator(subgraph_index);
int64_t node_exec_time = event->elapsed_time;
if (event->event_type == Profiler::EventType::OPERATOR_INVOKE_EVENT) {
// When recording an OPERATOR_INVOKE_EVENT, we have recorded the node
// index as event_metadata. See the macro
// TFLITE_SCOPED_TAGGED_OPERATOR_PROFILE defined in
// tensorflow/lite/core/api/profiler.h for details.
const auto node_index = event->event_metadata;
const auto op_details =
GetOperatorDetails(interpreter, subgraph_index, node_index);
std::string type_in_stats(event->tag);
if (!op_details.op_description.empty()) {
type_in_stats += "/" + op_details.op_description;
}
const auto node_name = ToString(op_details.outputs);
// Append node index to node name because 'stats_calculator' can not
// distinguish two nodes w/ the same 'node_name'.
const auto node_name_in_stats =
node_name + ":" + std::to_string(node_index);
stats_calculator->AddNodeStats(node_name_in_stats, type_in_stats,
node_num, node_exec_time, 0 /*memory */);
} else if (event->event_type ==
Profiler::EventType::DELEGATE_OPERATOR_INVOKE_EVENT) {
const std::string node_name(event->tag);
// Append event_metadata to node name because 'stats_calculator' can not
// distinguish two nodes w/ the same 'node_name'.
const auto node_name_in_stats =
"Delegate/" + node_name + ":" + std::to_string(event->event_metadata);
delegate_stats_calculator_->AddNodeStats(node_name_in_stats,
"DelegateOpInvoke", node_num,
node_exec_time, 0 /*memory */);
} else if (event->event_type ==
Profiler::EventType::DELEGATE_PROFILED_OPERATOR_INVOKE_EVENT) {
// This event type handles the delegate ops that are profiled in the
// Operator-wise Profiling section, not in the Delegate internal section.
const std::string node_name(event->tag);
// For delegate op, node name is treated as the type in stats.
const std::string type_in_stats(node_name);
// Append event_metadata to node name because 'stats_calculator' can not
// distinguish two nodes w/ the same 'node_name'.
const auto node_name_in_stats =
"Delegate/" + node_name + ":" + std::to_string(event->event_metadata);
stats_calculator->AddNodeStats(node_name_in_stats, type_in_stats,
node_num, node_exec_time, 0 /*memory */);
} else {
// Note: a different stats_calculator could be used to record
// non-op-invoke events so that these could be separated from
// op-invoke-events in the final profiling stats report.
const memory::MemoryUsage node_mem_usage =
event->end_mem_usage - event->begin_mem_usage;
std::string node_name(event->tag);
if (node_name == "Invoke") {
// Don't count the overall Invoke for profiling.
continue;
}
node_name += "/" + std::to_string(event->extra_event_metadata);
stats_calculator->AddNodeStats(node_name, event->tag, node_num,
node_exec_time,
node_mem_usage.mem_footprint_kb * 1000.0);
}
// Add total time except delegate ops that are profiled separately since the
// elapsed time of the delegate ops inside are already combined at a fused
// DELEGATE op.
if (event->event_type !=
Profiler::EventType::DELEGATE_OPERATOR_INVOKE_EVENT) {
total_us_per_subgraph_map[subgraph_index] += node_exec_time;
} else {
delegate_internal_total_us += node_exec_time;
}
++node_num;
}
for (auto& total_us_per_subgraph_pair : total_us_per_subgraph_map) {
auto stats_calculator =
GetStatsCalculator(total_us_per_subgraph_pair.first);
stats_calculator->UpdateRunTotalUs(total_us_per_subgraph_pair.second);
}
if (delegate_internal_total_us > 0) {
delegate_stats_calculator_->UpdateRunTotalUs(delegate_internal_total_us);
}
SetSubgraphNameMap(interpreter);
}
tensorflow::StatsCalculator* ProfileSummarizer::GetStatsCalculator(
uint32_t subgraph_index) {
if (stats_calculator_map_.count(subgraph_index) == 0) {
stats_calculator_map_[subgraph_index] =
std::make_unique<tensorflow::StatsCalculator>(
summary_formatter_->GetStatSummarizerOptions());
}
return stats_calculator_map_[subgraph_index].get();
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,92 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_PROFILING_PROFILE_SUMMARIZER_H_
#define TENSORFLOW_LITE_PROFILING_PROFILE_SUMMARIZER_H_
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "tensorflow/core/util/stats_calculator.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/profiling/profile_buffer.h"
#include "tensorflow/lite/profiling/profile_summary_formatter.h"
namespace tflite {
namespace profiling {
// Creates a summary of operator invocations in the interpreter.
class ProfileSummarizer {
public:
explicit ProfileSummarizer(
std::shared_ptr<ProfileSummaryFormatter> summary_formatter =
std::make_shared<ProfileSummaryDefaultFormatter>());
virtual ~ProfileSummarizer() {}
// Process profile events to update statistics for operator invocations.
void ProcessProfiles(const std::vector<const ProfileEvent*>& profile_stats,
const tflite::Interpreter& interpreter);
// Returns a string detailing the accumulated runtime stats in the format of
// summary_formatter_.
std::string GetOutputString() {
return summary_formatter_->GetOutputString(
stats_calculator_map_, *delegate_stats_calculator_, subgraph_name_map_);
}
std::string GetShortSummary() {
return summary_formatter_->GetShortSummary(
stats_calculator_map_, *delegate_stats_calculator_, subgraph_name_map_);
}
tensorflow::StatsCalculator* GetStatsCalculator(uint32_t subgraph_index);
bool HasProfiles() {
for (auto& stats_calc : stats_calculator_map_) {
auto subgraph_stats = stats_calc.second.get();
if (subgraph_stats->num_runs() >= 1) return true;
}
return false;
}
private:
// Map storing stats per subgraph.
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map_;
std::unique_ptr<tensorflow::StatsCalculator> delegate_stats_calculator_;
// Summary formatter for customized output formats.
std::shared_ptr<ProfileSummaryFormatter> summary_formatter_;
std::map<uint32_t, std::string> subgraph_name_map_;
void SetSubgraphNameMap(const tflite::Interpreter& interpreter) {
subgraph_name_map_.clear();
for (int subgraph_index = 0; subgraph_index < interpreter.subgraphs_size();
++subgraph_index) {
subgraph_name_map_[subgraph_index] =
interpreter.subgraph(subgraph_index)->GetName();
}
}
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_PROFILE_SUMMARIZER_H_
@@ -0,0 +1,230 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/profile_summarizer.h"
#include <string>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/kernels/subgraph_test_util.h"
#include "tensorflow/lite/kernels/test_util.h"
#include "tensorflow/lite/profiling/buffered_profiler.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace profiling {
namespace {
const char* kOpName = "SimpleOpEval";
TfLiteStatus SimpleOpEval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* input1;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, /*index=*/0, &input1));
const TfLiteTensor* input2;
TF_LITE_ENSURE_OK(context, GetInputSafe(context, node, /*index=*/1, &input2));
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context,
GetOutputSafe(context, node, /*index=*/0, &output));
int32_t* output_data = output->data.i32;
*output_data = *(input1->data.i32) + *(input2->data.i32);
return kTfLiteOk;
}
const char* SimpleOpProfilingString(const TfLiteContext* context,
const TfLiteNode* node) {
return "Profile";
}
TfLiteRegistration* RegisterSimpleOp() {
static TfLiteRegistration registration = {
nullptr, nullptr, nullptr,
SimpleOpEval, nullptr, tflite::BuiltinOperator_CUSTOM,
"SimpleOpEval", 1};
return &registration;
}
TfLiteRegistration* RegisterSimpleOpWithProfilingDetails() {
static TfLiteRegistration registration = {nullptr,
nullptr,
nullptr,
SimpleOpEval,
SimpleOpProfilingString,
tflite::BuiltinOperator_CUSTOM,
kOpName,
1};
return &registration;
}
class SimpleOpModel : public SingleOpModel {
public:
void Init(const std::function<TfLiteRegistration*()>& registration);
tflite::Interpreter* GetInterpreter() { return interpreter_.get(); }
void SetInputs(int32_t x, int32_t y) {
PopulateTensor(inputs_[0], {x});
PopulateTensor(inputs_[1], {y});
}
int32_t GetOutput() { return ExtractVector<int32_t>(output_)[0]; }
private:
int inputs_[2];
int output_;
};
void SimpleOpModel::Init(
const std::function<TfLiteRegistration*()>& registration) {
inputs_[0] = AddInput({TensorType_INT32, {1}});
inputs_[1] = AddInput({TensorType_INT32, {1}});
output_ = AddOutput({TensorType_INT32, {}});
SetCustomOp(kOpName, {}, registration);
BuildInterpreter({GetShape(inputs_[0]), GetShape(inputs_[1])});
}
TEST(ProfileSummarizerTest, Empty) {
ProfileSummarizer summarizer;
std::string output = summarizer.GetOutputString();
EXPECT_GT(output.size(), 0);
}
TEST(ProfileSummarizerTest, Interpreter) {
BufferedProfiler profiler(1024);
SimpleOpModel m;
m.Init(RegisterSimpleOp);
auto interpreter = m.GetInterpreter();
interpreter->SetProfiler(&profiler);
profiler.StartProfiling();
m.SetInputs(1, 2);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
// 3 = 1 + 2
EXPECT_EQ(m.GetOutput(), 3);
profiler.StopProfiling();
ProfileSummarizer summarizer;
auto events = profiler.GetProfileEvents();
EXPECT_EQ(2, events.size());
summarizer.ProcessProfiles(profiler.GetProfileEvents(), *interpreter);
auto output = summarizer.GetOutputString();
// TODO(shashishekhar): Add a better test here.
ASSERT_TRUE(output.find("SimpleOpEval") != std::string::npos) << output;
ASSERT_TRUE(output.find("Invoke") == std::string::npos) << output; // NOLINT
}
TEST(ProfileSummarizerTest, InterpreterPlusProfilingDetails) {
BufferedProfiler profiler(1024);
SimpleOpModel m;
m.Init(RegisterSimpleOpWithProfilingDetails);
auto interpreter = m.GetInterpreter();
interpreter->SetProfiler(&profiler);
profiler.StartProfiling();
m.SetInputs(1, 2);
ASSERT_EQ(m.Invoke(), kTfLiteOk);
// 3 = 1 + 2
EXPECT_EQ(m.GetOutput(), 3);
profiler.StopProfiling();
ProfileSummarizer summarizer;
auto events = profiler.GetProfileEvents();
EXPECT_EQ(2, events.size());
summarizer.ProcessProfiles(profiler.GetProfileEvents(), *interpreter);
auto output = summarizer.GetOutputString();
// TODO(shashishekhar): Add a better test here.
ASSERT_TRUE(output.find("SimpleOpEval/Profile") != std::string::npos)
<< output;
}
// A simple test that performs `ADD` if condition is true, and `MUL` otherwise.
// The computation is: `cond ? a + b : a * b`.
class ProfileSummarizerIfOpTest : public subgraph_test_util::ControlFlowOpTest {
protected:
void SetUp() override {
AddSubgraphs(2);
builder_->BuildAddSubgraph(interpreter_->subgraph(1));
builder_->BuildMulSubgraph(interpreter_->subgraph(2));
builder_->BuildIfSubgraph(&interpreter_->primary_subgraph());
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1});
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1, 2});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {5, 7});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[2]), {1, 2});
}
};
TEST_F(ProfileSummarizerIfOpTest, TestIfTrue) {
BufferedProfiler profiler(1024);
interpreter_->SetProfiler(&profiler);
interpreter_->typed_input_tensor<bool>(0)[0] = true;
profiler.StartProfiling();
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
profiler.StopProfiling();
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output, {1, 2}, {6, 9});
auto events = profiler.GetProfileEvents();
EXPECT_EQ(5, events.size());
int event_count_of_subgraph_zero = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 0; });
int event_count_of_subgraph_one = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 1; });
int event_count_of_subgraph_two = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 2; });
EXPECT_EQ(2, event_count_of_subgraph_zero);
EXPECT_EQ(3, event_count_of_subgraph_one);
EXPECT_EQ(0, event_count_of_subgraph_two);
}
TEST_F(ProfileSummarizerIfOpTest, TestIfFalse) {
BufferedProfiler profiler(1024);
interpreter_->SetProfiler(&profiler);
interpreter_->typed_input_tensor<bool>(0)[0] = false;
profiler.StartProfiling();
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
profiler.StopProfiling();
TfLiteTensor* output = interpreter_->tensor(interpreter_->outputs()[0]);
subgraph_test_util::CheckIntTensor(output, {1, 2}, {5, 14});
auto events = profiler.GetProfileEvents();
EXPECT_EQ(5, events.size());
int event_count_of_subgraph_zero = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 0; });
int event_count_of_subgraph_one = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 1; });
int event_count_of_subgraph_two = std::count_if(
events.begin(), events.end(),
[](auto event) { return event->extra_event_metadata == 2; });
EXPECT_EQ(2, event_count_of_subgraph_zero);
EXPECT_EQ(0, event_count_of_subgraph_one);
EXPECT_EQ(3, event_count_of_subgraph_two);
}
} // namespace
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,310 @@
/* Copyright 2020 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/profiling/profile_summary_formatter.h"
#include <fstream>
#include <iomanip>
#include <ios>
#include <map>
#include <memory>
#include <ostream>
#include <queue>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/core/util/stat_summarizer_options.h"
#include "tensorflow/core/util/stats_calculator.h"
#include "tensorflow/lite/profiling/proto/profiling_info.pb.h"
#include "tensorflow/lite/tools/logging.h"
namespace tflite {
namespace profiling {
std::string ProfileSummaryDefaultFormatter::GetOutputString(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const {
return GenerateReport("profile", /*include_output_string*/ true,
stats_calculator_map, delegate_stats_calculator,
subgraph_name_map);
}
std::string ProfileSummaryDefaultFormatter::GetShortSummary(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const {
return GenerateReport("summary", /*include_output_string*/ false,
stats_calculator_map, delegate_stats_calculator,
subgraph_name_map);
}
std::string ProfileSummaryDefaultFormatter::GenerateReport(
const std::string& tag, bool include_output_string,
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const {
std::stringstream stream;
bool has_non_primary_graph =
(stats_calculator_map.size() - stats_calculator_map.count(0)) > 0;
for (const auto& stats_calc : stats_calculator_map) {
auto subgraph_index = stats_calc.first;
auto subgraph_stats = stats_calc.second.get();
std::string subgraph_name = "";
if (subgraph_name_map.find(subgraph_index) != subgraph_name_map.end()) {
subgraph_name = subgraph_name_map.at(subgraph_index);
}
if (has_non_primary_graph) {
if (subgraph_index == 0) {
stream << "Primary graph (name: " << subgraph_name << ") " << tag << ":"
<< std::endl;
} else {
stream << "Subgraph (index: " << subgraph_index
<< ", name: " << subgraph_name << ") " << tag << ":"
<< std::endl;
}
}
if (include_output_string) {
stream << subgraph_stats->GetOutputString();
}
if (subgraph_index != 0) {
stream << "Subgraph (index: " << subgraph_index
<< ", name: " << subgraph_name << ") ";
}
stream << subgraph_stats->GetShortSummary() << std::endl;
}
if (delegate_stats_calculator.num_runs() > 0) {
stream << "Delegate internal: " << std::endl;
if (include_output_string) {
stream << delegate_stats_calculator.GetOutputString();
}
stream << delegate_stats_calculator.GetShortSummary() << std::endl;
}
return stream.str();
}
void ProfileSummaryDefaultFormatter::HandleOutput(
const std::string& init_output, const std::string& run_output,
std::string output_file_path) const {
std::ofstream output_file(output_file_path);
std::ostream* output_stream = nullptr;
if (output_file.good()) {
output_stream = &output_file;
}
if (!init_output.empty()) {
WriteOutput("Profiling Info for Benchmark Initialization:", init_output,
output_stream == nullptr ? &TFLITE_LOG(INFO) : output_stream);
}
if (!run_output.empty()) {
WriteOutput(
"Operator-wise Profiling Info for Regular Benchmark Runs:", run_output,
output_stream == nullptr ? &TFLITE_LOG(INFO) : output_stream);
}
}
tensorflow::StatSummarizerOptions
ProfileSummaryDefaultFormatter::GetStatSummarizerOptions() const {
auto options = tensorflow::StatSummarizerOptions();
// Summary will be manually handled per subgraphs in order to keep the
// compatibility.
options.show_summary = false;
options.show_memory = false;
return options;
}
tensorflow::StatSummarizerOptions
ProfileSummaryCSVFormatter::GetStatSummarizerOptions() const {
auto options = ProfileSummaryDefaultFormatter::GetStatSummarizerOptions();
options.format_as_csv = true;
return options;
}
std::vector<tensorflow::StatsCalculator::Detail>
ProfileSummaryProtoFormatter::GetDetailsSortedByRunOrder(
const tensorflow::StatsCalculator* stats_calculator) const {
std::vector<tensorflow::StatsCalculator::Detail> details;
std::map<std::string, tensorflow::StatsCalculator::Detail> unsorted_details =
stats_calculator->GetDetails();
std::priority_queue<
std::pair<std::string, const tensorflow::StatsCalculator::Detail*>>
sorted_list;
const int num_nodes = unsorted_details.size();
for (const auto& det : unsorted_details) {
const tensorflow::StatsCalculator::Detail* detail = &(det.second);
std::stringstream stream_for_sort;
stream_for_sort << std::setw(20) << std::right << std::setprecision(10)
<< std::fixed;
stream_for_sort << num_nodes - detail->run_order;
sorted_list.emplace(stream_for_sort.str(), detail);
}
while (!sorted_list.empty()) {
auto entry = sorted_list.top();
sorted_list.pop();
details.push_back(*entry.second);
}
return details;
}
void ProfileSummaryProtoFormatter::GenerateOpProfileDataFromDetail(
const tensorflow::StatsCalculator::Detail* detail,
const tensorflow::StatsCalculator* stats_calculator,
OpProfileData* const op_profile_data) const {
if (detail == nullptr) {
return;
}
op_profile_data->set_node_type(detail->type);
OpProfilingStat* inference_stat =
op_profile_data->mutable_inference_microseconds();
inference_stat->set_first(detail->elapsed_time.first());
inference_stat->set_last(detail->elapsed_time.newest());
inference_stat->set_avg(detail->elapsed_time.avg());
inference_stat->set_stddev(detail->elapsed_time.std_deviation());
inference_stat->set_variance(detail->elapsed_time.variance());
inference_stat->set_min(detail->elapsed_time.min());
inference_stat->set_max(detail->elapsed_time.max());
inference_stat->set_sum(detail->elapsed_time.sum());
inference_stat->set_count(detail->elapsed_time.count());
OpProfilingStat* memory_stat = op_profile_data->mutable_mem_kb();
memory_stat->set_first(detail->mem_used.first() / 1000.0);
memory_stat->set_last(detail->mem_used.newest() / 1000.0);
memory_stat->set_avg(detail->mem_used.avg() / 1000.0);
memory_stat->set_stddev(detail->mem_used.std_deviation() / 1000.0);
memory_stat->set_variance(detail->mem_used.variance() / 1000000.0);
memory_stat->set_min(detail->mem_used.min() / 1000.0);
memory_stat->set_max(detail->mem_used.max() / 1000.0);
memory_stat->set_sum(detail->mem_used.sum() / 1000.0);
memory_stat->set_count(detail->mem_used.count());
op_profile_data->set_times_called(detail->times_called /
stats_calculator->num_runs());
op_profile_data->set_name(detail->name);
op_profile_data->set_run_order(detail->run_order);
}
void ProfileSummaryProtoFormatter::GenerateSubGraphProfilingData(
const tensorflow::StatsCalculator* stats_calculator, int subgraph_index,
const std::map<uint32_t, std::string>& subgraph_name_map,
SubGraphProfilingData* const sub_graph_profiling_data) const {
sub_graph_profiling_data->set_subgraph_index(subgraph_index);
std::string subgraph_name = "";
if (subgraph_name_map.find(subgraph_index) != subgraph_name_map.end()) {
subgraph_name = subgraph_name_map.at(subgraph_index);
}
sub_graph_profiling_data->set_subgraph_name(subgraph_name);
for (tensorflow::StatsCalculator::Detail& detail :
GetDetailsSortedByRunOrder(stats_calculator)) {
OpProfileData* const op_profile_data =
sub_graph_profiling_data->add_per_op_profiles();
GenerateOpProfileDataFromDetail(&detail, stats_calculator, op_profile_data);
}
}
void ProfileSummaryProtoFormatter::GenerateDelegateProfilingData(
const tensorflow::StatsCalculator* stats_calculator,
DelegateProfilingData* const delegate_profiling_data) const {
for (const tensorflow::StatsCalculator::Detail& detail :
GetDetailsSortedByRunOrder(stats_calculator)) {
OpProfileData* const op_profile_data =
delegate_profiling_data->add_per_op_profiles();
GenerateOpProfileDataFromDetail(&detail, stats_calculator, op_profile_data);
}
}
std::string ProfileSummaryProtoFormatter::GetShortSummary(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const {
TFLITE_LOG(ERROR) << "GetShortSummary is not supported for proto formatter.";
return "";
}
std::string ProfileSummaryProtoFormatter::GetOutputString(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const {
ModelProfilingData model_profiling_data;
for (const auto& stats_calc : stats_calculator_map) {
auto subgraph_index = stats_calc.first;
tensorflow::StatsCalculator* subgraph_stats = stats_calc.second.get();
SubGraphProfilingData* const sub_graph_profiling_data =
model_profiling_data.add_subgraph_profiles();
GenerateSubGraphProfilingData(subgraph_stats, subgraph_index,
subgraph_name_map, sub_graph_profiling_data);
}
if (delegate_stats_calculator.num_runs() > 0) {
DelegateProfilingData* const delegate_profiling_data =
model_profiling_data.add_delegate_profiles();
GenerateDelegateProfilingData(&delegate_stats_calculator,
delegate_profiling_data);
}
return model_profiling_data.SerializeAsString();
}
tensorflow::StatSummarizerOptions
ProfileSummaryProtoFormatter::GetStatSummarizerOptions() const {
auto options = tensorflow::StatSummarizerOptions();
// Summary will be manually handled per subgraphs in order to keep the
// compatibility.
options.show_summary = false;
options.show_memory = false;
return options;
}
void ProfileSummaryProtoFormatter::HandleOutput(
const std::string& init_output, const std::string& run_output,
std::string output_file_path) const {
std::ofstream output_file(output_file_path, std::ios_base::binary);
std::ostream* output_stream = nullptr;
if (output_file.good()) {
output_stream = &output_file;
}
BenchmarkProfilingData benchmark_profiling_data;
if (!init_output.empty()) {
benchmark_profiling_data.mutable_init_profile()->ParseFromString(
init_output);
}
if (!run_output.empty()) {
benchmark_profiling_data.mutable_runtime_profile()->ParseFromString(
run_output);
}
if (output_stream == nullptr) {
TFLITE_LOG(INFO) << benchmark_profiling_data.DebugString();
} else {
benchmark_profiling_data.SerializeToOstream(output_stream);
}
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,146 @@
/* Copyright 2020 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_PROFILING_PROFILE_SUMMARY_FORMATTER_H_
#define TENSORFLOW_LITE_PROFILING_PROFILE_SUMMARY_FORMATTER_H_
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <functional>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "tensorflow/core/util/stat_summarizer_options.h"
#include "tensorflow/core/util/stats_calculator.h"
#include "tensorflow/lite/profiling/proto/profiling_info.pb.h"
namespace tflite {
namespace profiling {
// Formats the profile summary in a certain way.
class ProfileSummaryFormatter {
public:
ProfileSummaryFormatter() = default;
virtual ~ProfileSummaryFormatter() {}
// Returns a string detailing the accumulated runtime stats in StatsCalculator
// of ProfileSummarizer.
virtual std::string GetOutputString(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const = 0;
// Returns a string detailing the short summary of the accumulated runtime
// stats in StatsCalculator of ProfileSummarizer.
virtual std::string GetShortSummary(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const = 0;
virtual tensorflow::StatSummarizerOptions GetStatSummarizerOptions()
const = 0;
virtual void HandleOutput(const std::string& init_output,
const std::string& run_output,
std::string output_file_path) const = 0;
};
class ProfileSummaryDefaultFormatter : public ProfileSummaryFormatter {
public:
ProfileSummaryDefaultFormatter() = default;
~ProfileSummaryDefaultFormatter() override {}
std::string GetOutputString(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const override;
std::string GetShortSummary(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const override;
tensorflow::StatSummarizerOptions GetStatSummarizerOptions() const override;
void HandleOutput(const std::string& init_output,
const std::string& run_output,
std::string output_file_path) const override;
private:
std::string GenerateReport(
const std::string& tag, bool include_output_string,
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const;
void WriteOutput(const std::string& header, const std::string& data,
std::ostream* stream) const {
(*stream) << header << std::endl;
(*stream) << data << std::endl;
}
};
class ProfileSummaryCSVFormatter : public ProfileSummaryDefaultFormatter {
public:
ProfileSummaryCSVFormatter() = default;
tensorflow::StatSummarizerOptions GetStatSummarizerOptions() const override;
};
class ProfileSummaryProtoFormatter : public ProfileSummaryFormatter {
public:
std::string GetOutputString(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const override;
std::string GetShortSummary(
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const override;
tensorflow::StatSummarizerOptions GetStatSummarizerOptions() const override;
void HandleOutput(const std::string& init_output,
const std::string& run_output,
std::string output_file_path) const override;
private:
std::string GenerateReport(
const std::string& tag, bool include_output_string,
const std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>&
stats_calculator_map,
const tensorflow::StatsCalculator& delegate_stats_calculator,
const std::map<uint32_t, std::string>& subgraph_name_map) const;
void GenerateSubGraphProfilingData(
const tensorflow::StatsCalculator* stats_calculator, int subgraph_index,
const std::map<uint32_t, std::string>& subgraph_name_map,
SubGraphProfilingData* sub_graph_profiling_data) const;
void GenerateDelegateProfilingData(
const tensorflow::StatsCalculator* stats_calculator,
DelegateProfilingData* delegate_profiling_data) const;
void GenerateOpProfileDataFromDetail(
const tensorflow::StatsCalculator::Detail* detail,
const tensorflow::StatsCalculator* stats_calculator,
OpProfileData* op_profile_data) const;
std::vector<tensorflow::StatsCalculator::Detail> GetDetailsSortedByRunOrder(
const tensorflow::StatsCalculator* stats_calculator) const;
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_PROFILE_SUMMARY_FORMATTER_H_
@@ -0,0 +1,530 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/profile_summary_formatter.h"
#include <cstddef>
#include <fstream>
#include <ios>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <gtest/gtest.h>
#include "absl/strings/match.h"
#include "tensorflow/core/util/stat_summarizer_options.h"
#include "tensorflow/core/util/stats_calculator.h"
#include "tensorflow/lite/profiling/proto/profiling_info.pb.h"
namespace tflite {
namespace profiling {
namespace {
// LINT.IfChange(OpProfilingStatComparator)
bool AreOpProfilingStatEqual(const OpProfilingStat& op_profiling_stat_1,
const OpProfilingStat& op_profiling_stat_2) {
auto proto_to_tuple = [](const OpProfilingStat& op_profiling_stat) {
return std::make_tuple(op_profiling_stat.first(), op_profiling_stat.last(),
op_profiling_stat.avg(), op_profiling_stat.stddev(),
op_profiling_stat.variance(),
op_profiling_stat.min(), op_profiling_stat.max(),
op_profiling_stat.sum(), op_profiling_stat.count());
};
return proto_to_tuple(op_profiling_stat_1) ==
proto_to_tuple(op_profiling_stat_2);
}
// LINT.ThenChange(//tensorflow/lite/profiling/proto/profiling_info.proto:OpProfilingStat)
// LINT.IfChange(OpProfileDataComparator)
bool AreOpProfileDataEqual(const OpProfileData& op_profile_data_1,
const OpProfileData& op_profile_data_2) {
auto proto_to_tuple = [](const OpProfileData& op_profile_data) {
return std::make_tuple(op_profile_data.node_type(),
op_profile_data.times_called(),
op_profile_data.name(), op_profile_data.run_order());
};
return (proto_to_tuple(op_profile_data_1) ==
proto_to_tuple(op_profile_data_2)) &&
AreOpProfilingStatEqual(op_profile_data_1.inference_microseconds(),
op_profile_data_2.inference_microseconds()) &&
(AreOpProfilingStatEqual(op_profile_data_1.mem_kb(),
op_profile_data_2.mem_kb()));
}
// LINT.ThenChange(//tensorflow/lite/profiling/proto/profiling_info.proto:OpProfileData)
// LINT.IfChange(SubGraphProfilingDataComparator)
bool AreSubGraphProfilingDataEqual(
const SubGraphProfilingData& subgraph_profiling_data_1,
const SubGraphProfilingData& subgraph_profiling_data_2) {
auto proto_to_tuple =
[](const SubGraphProfilingData& subgraph_profiling_data) {
return std::make_tuple(
subgraph_profiling_data.subgraph_name(),
subgraph_profiling_data.per_op_profiles().size());
};
if (proto_to_tuple(subgraph_profiling_data_1) ==
proto_to_tuple(subgraph_profiling_data_2)) {
for (size_t i = 0; i < subgraph_profiling_data_1.per_op_profiles().size();
++i) {
auto op_profile_data_1 = subgraph_profiling_data_1.per_op_profiles(i);
auto op_profile_data_2 = subgraph_profiling_data_2.per_op_profiles(i);
if (!AreOpProfileDataEqual(op_profile_data_1, op_profile_data_2)) {
return false;
}
}
return true;
}
return false;
}
// LINT.ThenChange(//tensorflow/lite/profiling/proto/profiling_info.proto:SubGraphProfilingData)
// LINT.IfChange(DelegateProfilingDataComparator)
bool AreDelegateProfilingDataEqual(
const DelegateProfilingData& delegate_profiling_data_1,
const DelegateProfilingData& delegate_profiling_data_2) {
auto proto_to_tuple =
[](const DelegateProfilingData& delegate_profiling_data) {
return std::make_tuple(
delegate_profiling_data.delegate_name(),
delegate_profiling_data.per_op_profiles().size());
};
if (proto_to_tuple(delegate_profiling_data_1) ==
proto_to_tuple(delegate_profiling_data_2)) {
for (size_t i = 0; i < delegate_profiling_data_1.per_op_profiles().size();
++i) {
auto op_profile_data_1 = delegate_profiling_data_1.per_op_profiles(i);
auto op_profile_data_2 = delegate_profiling_data_2.per_op_profiles(i);
if (!AreOpProfileDataEqual(op_profile_data_1, op_profile_data_2)) {
return false;
}
}
return true;
}
return false;
}
// LINT.ThenChange(//tensorflow/lite/profiling/proto/profiling_info.proto:DelegateProfilingData)
// LINT.IfChange(ModelProfilingDataComparator)
bool AreModelProfilingDataEqual(
const ModelProfilingData& model_profiling_data_1,
const ModelProfilingData& model_profiling_data_2) {
if (model_profiling_data_1.subgraph_profiles().size() !=
model_profiling_data_2.subgraph_profiles().size()) {
return false;
}
for (size_t i = 0; i < model_profiling_data_1.subgraph_profiles().size();
++i) {
auto subgraph_profile_1 = model_profiling_data_1.subgraph_profiles(i);
auto subgraph_profile_2 = model_profiling_data_2.subgraph_profiles(i);
if (!AreSubGraphProfilingDataEqual(subgraph_profile_1,
subgraph_profile_2)) {
return false;
}
}
if (model_profiling_data_1.delegate_profiles().size() !=
model_profiling_data_2.delegate_profiles().size()) {
return false;
}
for (size_t i = 0; i < model_profiling_data_1.delegate_profiles().size();
++i) {
auto delegate_profile_1 = model_profiling_data_1.delegate_profiles(i);
auto delegate_profile_2 = model_profiling_data_2.delegate_profiles(i);
if (!AreDelegateProfilingDataEqual(delegate_profile_1,
delegate_profile_2)) {
return false;
}
}
return true;
}
// LINT.ThenChange(//tensorflow/lite/profiling/proto/profiling_info.proto:ModelProfilingData)
TEST(SummaryWriterTest, SummaryOptionStdOut) {
ProfileSummaryDefaultFormatter writer;
tensorflow::StatSummarizerOptions options = writer.GetStatSummarizerOptions();
EXPECT_EQ(options.show_summary, false);
EXPECT_EQ(options.show_memory, false);
EXPECT_EQ(options.format_as_csv, false);
}
TEST(SummaryWriterTest, SummaryOptionCSV) {
ProfileSummaryCSVFormatter writer;
tensorflow::StatSummarizerOptions options = writer.GetStatSummarizerOptions();
EXPECT_EQ(options.show_summary, false);
EXPECT_EQ(options.show_memory, false);
EXPECT_EQ(options.format_as_csv, true);
}
TEST(SummaryWriterTest, EmptyOutputString) {
ProfileSummaryDefaultFormatter writer;
std::string output = writer.GetOutputString(
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>(),
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()), {});
EXPECT_EQ(output.size(), 0);
}
TEST(SummaryWriterTest, EmptyShortSummary) {
ProfileSummaryDefaultFormatter writer;
std::string output = writer.GetShortSummary(
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>(),
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()), {});
EXPECT_EQ(output.size(), 0);
}
TEST(SummaryWriterTest, SingleSubgraphOutputString) {
ProfileSummaryDefaultFormatter writer;
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map;
stats_calculator_map[0] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
std::string output = writer.GetOutputString(
stats_calculator_map,
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()), {});
ASSERT_TRUE(absl::StrContains(output, "Run Order"));
ASSERT_TRUE(absl::StrContains(output, "Top by Computation Time"));
ASSERT_TRUE(!absl::StrContains(output, "Top by Memory Use"));
ASSERT_TRUE(absl::StrContains(output, "Summary by node type"));
ASSERT_TRUE(absl::StrContains(output, "nodes observed"));
ASSERT_TRUE(!absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(!absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(!absl::StrContains(output, "Delegate internal"));
}
TEST(SummaryWriterTest, SingleSubgraphShortSummary) {
ProfileSummaryDefaultFormatter writer;
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map;
stats_calculator_map[0] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
std::string output = writer.GetShortSummary(
stats_calculator_map,
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()),
{{0, "Primary graph"}});
ASSERT_TRUE(!absl::StrContains(output, "Run Order"));
ASSERT_TRUE(!absl::StrContains(output, "Top by Computation Time"));
ASSERT_TRUE(!absl::StrContains(output, "Top by Memory Use"));
ASSERT_TRUE(!absl::StrContains(output, "Summary by node type"));
ASSERT_TRUE(absl::StrContains(output, "nodes observed"));
ASSERT_TRUE(!absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(!absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(!absl::StrContains(output, "Delegate internal"));
}
TEST(SummaryWriterTest, MultiSubgraphOutputString) {
ProfileSummaryDefaultFormatter writer;
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map;
stats_calculator_map[0] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
stats_calculator_map[1] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
std::string output = writer.GetOutputString(
stats_calculator_map,
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()),
{{0, "Primary graph"}, {1, "Subgraph 1"}});
ASSERT_TRUE(absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(!absl::StrContains(output, "Delegate internal"));
}
TEST(SummaryWriterTest, MultiSubgraphOutputStringForProto) {
ProfileSummaryProtoFormatter writer;
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map;
stats_calculator_map[0] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
std::string kernel_name_1 = "Kernel 1";
std::string kernel_name_2 = "Kernel 2";
std::string kernel_name_3 = "Kernel 3";
std::string op_name_1 = "Convolution";
std::string op_name_2 = "Reshape";
std::string op_name_3 = "Convolution";
stats_calculator_map[0]->AddNodeStats(kernel_name_1, op_name_1, 1, 10, 10000);
stats_calculator_map[0]->AddNodeStats(kernel_name_1, op_name_1, 1, 20, 20000);
stats_calculator_map[0]->AddNodeStats(kernel_name_2, op_name_2, 2, 15, 10000);
stats_calculator_map[0]->UpdateRunTotalUs(25);
stats_calculator_map[1] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
stats_calculator_map[1]->AddNodeStats(kernel_name_3, op_name_3, 3, 10, 10000);
stats_calculator_map[1]->UpdateRunTotalUs(10);
std::string output = writer.GetOutputString(
stats_calculator_map,
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()),
{{0, "Primary graph"}, {1, "Subgraph 1"}});
ModelProfilingData model_profiling_data;
model_profiling_data.ParseFromString(output);
ASSERT_TRUE(absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(!absl::StrContains(output, "Delegate internal"));
ASSERT_EQ(model_profiling_data.subgraph_profiles().size(), 2);
ASSERT_EQ(model_profiling_data.subgraph_profiles(0).subgraph_name(),
"Primary graph");
ASSERT_EQ(model_profiling_data.subgraph_profiles(0).per_op_profiles().size(),
2);
OpProfileData op_profile_data_1;
op_profile_data_1.set_node_type(op_name_1);
OpProfilingStat* inference_microseconds_stat_1 =
op_profile_data_1.mutable_inference_microseconds();
inference_microseconds_stat_1->set_first(10);
inference_microseconds_stat_1->set_last(20);
inference_microseconds_stat_1->set_max(20);
inference_microseconds_stat_1->set_min(10);
inference_microseconds_stat_1->set_avg(15);
inference_microseconds_stat_1->set_stddev(5);
inference_microseconds_stat_1->set_variance(25);
inference_microseconds_stat_1->set_sum(30);
inference_microseconds_stat_1->set_count(2);
OpProfilingStat* memory_stat_1 = op_profile_data_1.mutable_mem_kb();
memory_stat_1->set_first(10);
memory_stat_1->set_last(20);
memory_stat_1->set_max(20);
memory_stat_1->set_min(10);
memory_stat_1->set_avg(15);
memory_stat_1->set_stddev(5);
memory_stat_1->set_variance(25);
memory_stat_1->set_sum(30);
memory_stat_1->set_count(2);
op_profile_data_1.set_name(kernel_name_1);
op_profile_data_1.set_run_order(1);
op_profile_data_1.set_times_called(2);
EXPECT_TRUE(AreOpProfileDataEqual(
model_profiling_data.subgraph_profiles(0).per_op_profiles(0),
op_profile_data_1));
OpProfileData op_profile_data_2;
op_profile_data_2.set_node_type(op_name_2);
OpProfilingStat* inference_microseconds_stat_2 =
op_profile_data_2.mutable_inference_microseconds();
inference_microseconds_stat_2->set_first(15);
inference_microseconds_stat_2->set_last(15);
inference_microseconds_stat_2->set_max(15);
inference_microseconds_stat_2->set_min(15);
inference_microseconds_stat_2->set_avg(15);
inference_microseconds_stat_2->set_stddev(0);
inference_microseconds_stat_2->set_variance(0);
inference_microseconds_stat_2->set_sum(15);
inference_microseconds_stat_2->set_count(1);
OpProfilingStat* memory_stat_2 = op_profile_data_2.mutable_mem_kb();
memory_stat_2->set_first(10);
memory_stat_2->set_last(10);
memory_stat_2->set_max(10);
memory_stat_2->set_min(10);
memory_stat_2->set_avg(10);
memory_stat_2->set_stddev(0);
memory_stat_2->set_variance(0);
memory_stat_2->set_sum(10);
memory_stat_2->set_count(1);
op_profile_data_2.set_times_called(1);
op_profile_data_2.set_name(kernel_name_2);
op_profile_data_2.set_run_order(2);
EXPECT_TRUE(AreOpProfileDataEqual(
model_profiling_data.subgraph_profiles(0).per_op_profiles(1),
op_profile_data_2));
ASSERT_EQ(model_profiling_data.subgraph_profiles(1).subgraph_name(),
"Subgraph 1");
ASSERT_EQ(model_profiling_data.subgraph_profiles(1).per_op_profiles().size(),
1);
OpProfileData op_profile_data_3;
op_profile_data_3.set_node_type(op_name_3);
OpProfilingStat* inference_microseconds_stat_3 =
op_profile_data_3.mutable_inference_microseconds();
inference_microseconds_stat_3->set_first(10);
inference_microseconds_stat_3->set_last(10);
inference_microseconds_stat_3->set_max(10);
inference_microseconds_stat_3->set_min(10);
inference_microseconds_stat_3->set_avg(10);
inference_microseconds_stat_3->set_stddev(0);
inference_microseconds_stat_3->set_variance(0);
inference_microseconds_stat_3->set_sum(10);
inference_microseconds_stat_3->set_count(1);
OpProfilingStat* memory_stat_3 = op_profile_data_3.mutable_mem_kb();
memory_stat_3->set_first(10);
memory_stat_3->set_last(10);
memory_stat_3->set_max(10);
memory_stat_3->set_min(10);
memory_stat_3->set_avg(10);
memory_stat_3->set_stddev(0);
memory_stat_3->set_variance(0);
memory_stat_3->set_sum(10);
memory_stat_3->set_count(1);
op_profile_data_3.set_times_called(1);
op_profile_data_3.set_name(kernel_name_3);
op_profile_data_3.set_run_order(3);
EXPECT_TRUE(AreOpProfileDataEqual(
model_profiling_data.subgraph_profiles(1).per_op_profiles(0),
op_profile_data_3));
}
TEST(SummaryWriterTest, MultiSubgraphHandleOutputForProto) {
ProfileSummaryProtoFormatter writer;
ModelProfilingData model_profiling_data_run;
SubGraphProfilingData* subgraph_profiling_data =
model_profiling_data_run.add_subgraph_profiles();
subgraph_profiling_data->set_subgraph_name("Primary graph");
OpProfileData* op_profile_data_1 =
subgraph_profiling_data->add_per_op_profiles();
op_profile_data_1->set_node_type("Convolution");
OpProfilingStat* inference_stat_1 =
op_profile_data_1->mutable_inference_microseconds();
inference_stat_1->set_first(10);
inference_stat_1->set_avg(10);
OpProfilingStat* mem_stat_1 = op_profile_data_1->mutable_mem_kb();
mem_stat_1->set_first(10);
mem_stat_1->set_avg(10);
op_profile_data_1->set_times_called(1);
op_profile_data_1->set_name("Kernel 1");
op_profile_data_1->set_run_order(1);
OpProfileData* op_profile_data_2 =
subgraph_profiling_data->add_per_op_profiles();
op_profile_data_2->set_node_type("Reshape");
OpProfilingStat* inference_stat_2 =
op_profile_data_2->mutable_inference_microseconds();
inference_stat_2->set_first(15);
inference_stat_2->set_avg(15);
OpProfilingStat* mem_stat_2 = op_profile_data_2->mutable_mem_kb();
mem_stat_2->set_first(10);
mem_stat_2->set_avg(10);
op_profile_data_2->set_times_called(1);
op_profile_data_2->set_name("Kernel 2");
op_profile_data_2->set_run_order(2);
SubGraphProfilingData* subgraph_profiling_data_1 =
model_profiling_data_run.add_subgraph_profiles();
subgraph_profiling_data_1->set_subgraph_name("Subgraph 1");
OpProfileData* op_profile_data_3 =
subgraph_profiling_data_1->add_per_op_profiles();
op_profile_data_3->set_node_type("Convolution");
OpProfilingStat* inference_stat_3 =
op_profile_data_3->mutable_inference_microseconds();
inference_stat_3->set_first(10);
inference_stat_3->set_avg(10);
OpProfilingStat* mem_stat_3 = op_profile_data_3->mutable_mem_kb();
mem_stat_3->set_first(10);
mem_stat_3->set_avg(10);
op_profile_data_3->set_times_called(1);
op_profile_data_3->set_name("Kernel 3");
op_profile_data_3->set_run_order(3);
DelegateProfilingData* delegate_profiling_data =
model_profiling_data_run.add_delegate_profiles();
OpProfileData* op_profile_data_4 =
delegate_profiling_data->add_per_op_profiles();
op_profile_data_4->set_node_type("Convolution");
OpProfilingStat* inference_stat_4 =
op_profile_data_4->mutable_inference_microseconds();
inference_stat_4->set_first(10);
inference_stat_4->set_avg(10);
OpProfilingStat* mem_stat_4 = op_profile_data_4->mutable_mem_kb();
mem_stat_4->set_first(10);
mem_stat_4->set_avg(10);
op_profile_data_4->set_times_called(1);
op_profile_data_4->set_name("Kernel 4");
op_profile_data_4->set_run_order(4);
ModelProfilingData model_profiling_data_init;
SubGraphProfilingData* subgraph_profiling_data_init =
model_profiling_data_init.add_subgraph_profiles();
subgraph_profiling_data_init->set_subgraph_name("Primary graph");
OpProfileData* op_profile_data_init_1 =
subgraph_profiling_data_init->add_per_op_profiles();
op_profile_data_init_1->set_node_type("Convolution");
OpProfilingStat* inference_stat_init_1 =
op_profile_data_init_1->mutable_inference_microseconds();
inference_stat_init_1->set_first(10);
inference_stat_init_1->set_avg(10);
op_profile_data_init_1->set_times_called(1);
OpProfilingStat* mem_stat_init_1 = op_profile_data_init_1->mutable_mem_kb();
mem_stat_init_1->set_first(10);
mem_stat_init_1->set_avg(10);
op_profile_data_init_1->set_name("ModifyGraphWithDelegate");
op_profile_data_init_1->set_run_order(1);
#ifdef __ANDROID__
std::string file_name = "/data/local/tmp/test_file.proto";
#else
std::string file_name = "/tmp/test_file.proto";
#endif
writer.HandleOutput(model_profiling_data_init.SerializeAsString(),
model_profiling_data_run.SerializeAsString(), file_name);
std::ifstream file(file_name, std::ios::binary);
ASSERT_TRUE(file.good());
BenchmarkProfilingData benchmark_profiling_data;
benchmark_profiling_data.ParseFromIstream(&file);
file.close();
ASSERT_TRUE(benchmark_profiling_data.model_name().empty());
EXPECT_TRUE(AreModelProfilingDataEqual(
benchmark_profiling_data.init_profile(), model_profiling_data_init));
EXPECT_TRUE(AreModelProfilingDataEqual(
benchmark_profiling_data.runtime_profile(), model_profiling_data_run));
}
TEST(SummaryWriterTest, MultiSubgraphShortSummary) {
ProfileSummaryDefaultFormatter writer;
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>
stats_calculator_map;
stats_calculator_map[0] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
stats_calculator_map[1] = std::make_unique<tensorflow::StatsCalculator>(
writer.GetStatSummarizerOptions());
std::string output = writer.GetShortSummary(
stats_calculator_map,
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions()),
{{0, "Primary graph"}, {1, "Subgraph 1"}});
ASSERT_TRUE(absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(!absl::StrContains(output, "Delegate internal"));
}
TEST(SummaryWriterTest, DelegationOutputString) {
ProfileSummaryDefaultFormatter writer;
auto delegate_stats_calculator =
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions());
delegate_stats_calculator.UpdateRunTotalUs(1);
std::string output = writer.GetOutputString(
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>(),
delegate_stats_calculator, {});
ASSERT_TRUE(!absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(!absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(absl::StrContains(output, "Delegate internal"));
}
TEST(SummaryWriterTest, DelegationShortSummary) {
ProfileSummaryDefaultFormatter writer;
auto delegate_stats_calculator =
tensorflow::StatsCalculator(writer.GetStatSummarizerOptions());
delegate_stats_calculator.UpdateRunTotalUs(1);
std::string output = writer.GetShortSummary(
std::map<uint32_t, std::unique_ptr<tensorflow::StatsCalculator>>(),
delegate_stats_calculator, {});
ASSERT_TRUE(!absl::StrContains(output, "Primary graph"));
ASSERT_TRUE(!absl::StrContains(output, "Subgraph"));
ASSERT_TRUE(absl::StrContains(output, "Delegate internal"));
}
} // namespace
} // namespace profiling
} // namespace tflite
+36
View File
@@ -0,0 +1,36 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_PROFILING_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_PROFILER_H_
#include "tensorflow/lite/profiling/buffered_profiler.h"
#include "tensorflow/lite/profiling/noop_profiler.h"
namespace tflite {
namespace profiling {
// TODO(b/131688504): Remove this and use runtime flags for profiler selection.
#ifdef TFLITE_PROFILING_ENABLED
using Profiler = BufferedProfiler;
#else
using Profiler = NoopProfiler;
#endif // TFLITE_PROFILING_ENABLED
} // namespace profiling
} // namespace tflite
#define SCOPED_TAGGED_OPERATOR_PROFILE TFLITE_SCOPED_TAGGED_OPERATOR_PROFILE
#endif // TENSORFLOW_LITE_PROFILING_PROFILER_H_
@@ -0,0 +1,79 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow:tensorflow.default.bzl", "pybind_extension")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "tfl_calibration_utils",
srcs = ["tfl_calibration_utils.cc"],
hdrs = ["tfl_calibration_utils.h"],
deps = [
":tfl_tensor_stats_profiler",
"//tensorflow/lite:framework",
"//tensorflow/lite:stateful_error_reporter",
"//tensorflow/lite/c:common",
"//tensorflow/lite/kernels:builtin_ops",
"//tensorflow/lite/profiling:memory_info",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@eigen_archive//:eigen3",
],
)
cc_library(
name = "tfl_tensor_stats_profiler",
srcs = ["tfl_tensor_stats_profiler.cc"],
hdrs = ["tfl_tensor_stats_profiler.h"],
deps = [
"//tensorflow/lite:framework",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite/c:common",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:private_common",
],
)
cc_test(
name = "tfl_tensor_stats_profiler_test",
srcs = ["tfl_tensor_stats_profiler_test.cc"],
deps = [
":tfl_tensor_stats_profiler",
"//tensorflow/lite:framework",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:private_common",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "tfl_calibration_utils_test",
srcs = ["tfl_calibration_utils_test.cc"],
deps = [
":tfl_calibration_utils",
"//tensorflow/lite:framework",
"//tensorflow/lite/profiling:memory_info",
"@com_google_absl//absl/status:statusor",
"@com_google_googletest//:gtest_main",
],
)
pybind_extension(
name = "_pywrap_tfl_calibration",
srcs = ["tfl_calibration_wrapper.cc"],
module_name = "_pywrap_tfl_calibration",
wrap_py_init = True,
deps = [
":tfl_calibration_utils",
"//tensorflow/lite:framework",
"//tensorflow/lite:stateful_error_reporter",
"//tensorflow/lite/profiling:memory_info",
"@pybind11",
"@xla//third_party/python_runtime:headers",
],
)
@@ -0,0 +1,106 @@
/* Copyright 2026 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/profiling/profiler_based_calibration/tfl_calibration_utils.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "Eigen/Core" // from @eigen_archive
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/profiling/profiler_based_calibration/tfl_tensor_stats_profiler.h"
#include "tensorflow/lite/stateful_error_reporter.h"
namespace odml {
namespace {
template <typename T>
void UpdateTensorStats(const T* tensor_buffer, int num_elements,
const char* tensor_name,
std::map<std::string, Range>& tensor_stats) {
if (num_elements <= 0) {
return;
}
const Eigen::Map<const Eigen::VectorX<T>> vec(tensor_buffer, num_elements);
const float current_min = static_cast<float>(vec.minCoeff());
const float current_max = static_cast<float>(vec.maxCoeff());
auto [it, inserted] =
tensor_stats.insert({tensor_name, Range{current_min, current_max}});
if (!inserted) {
it->second.min = std::min(it->second.min, current_min);
it->second.max = std::max(it->second.max, current_max);
}
}
} // namespace
absl::StatusOr<std::map<std::string, Range>> InvokeWithCalibration(
tflite::Interpreter* interpreter, int subgraph_index,
tflite::StatefulErrorReporter* reporter) {
// Use a callback to capture tensor stats during operator invocation. The
// results are stored in a map of tensor name to min/max stat value.
std::map<std::string, Range> tensor_stats;
auto calc_tensor_stats = [&](const TfLiteTensor* tensor) {
// Skip constant tensors.
if (tensor->allocation_type == kTfLiteMmapRo ||
tensor->allocation_type == kTfLitePersistentRo) {
return;
}
// Skip empty or unallocated tensors.
if (tensor->data.raw == nullptr) {
return;
}
if (tensor->type == kTfLiteFloat32) {
UpdateTensorStats<float>(tensor->data.f, tensor->bytes / sizeof(float),
tensor->name, tensor_stats);
} else if (tensor->type == kTfLiteInt32) {
UpdateTensorStats<int32_t>(tensor->data.i32,
tensor->bytes / sizeof(int32_t), tensor->name,
tensor_stats);
}
};
auto profiler = std::make_unique<odml::TensorStatsProfiler>(
*interpreter, calc_tensor_stats);
interpreter->SetProfiler(profiler.get());
auto invoke_status = interpreter->subgraph(subgraph_index)->Invoke();
// Reset the profiler to avoid dangling pointer issues when interpreter is
// reused.
interpreter->SetProfiler(nullptr);
for (size_t i = 0; i < interpreter->subgraphs_size(); ++i) {
interpreter->subgraph(i)->SetProfiler(nullptr, i);
}
if (invoke_status != kTfLiteOk) {
return absl::InternalError("InvokeWithCalibration failed" +
(reporter ? ": " + reporter->message() : ""));
}
return tensor_stats;
}
tflite::profiling::memory::MemoryUsage GetMemoryUsage() {
return tflite::profiling::memory::GetMemoryUsage();
}
} // namespace odml
@@ -0,0 +1,47 @@
/* Copyright 2026 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_PROFILING_PROFILER_BASED_CALIBRATION_TFL_CALIBRATION_UTILS_H_
#define TENSORFLOW_LITE_PROFILING_PROFILER_BASED_CALIBRATION_TFL_CALIBRATION_UTILS_H_
#include <map>
#include <string>
#include "absl/status/statusor.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/stateful_error_reporter.h"
namespace odml {
struct Range {
float min;
float max;
};
// Invokes the subgraph at `subgraph_index` using the specified `interpreter`,
// and returns a map of min/max values for each non-constant tensor encountered
// during execution. Currently min/max collection supports only float32 and
// int32 tensors, all other tensors are ignored.
absl::StatusOr<std::map<std::string, Range>> InvokeWithCalibration(
tflite::Interpreter* interpreter, int subgraph_index,
tflite::StatefulErrorReporter* reporter = nullptr);
// Gets memory usage stats.
tflite::profiling::memory::MemoryUsage GetMemoryUsage();
} // namespace odml
#endif // TENSORFLOW_LITE_PROFILING_PROFILER_BASED_CALIBRATION_TFL_CALIBRATION_UTILS_H_
@@ -0,0 +1,132 @@
/* Copyright 2026 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/profiling/profiler_based_calibration/tfl_calibration_utils.h"
#include <cstdint>
#include <cstring>
#include <memory>
#include <gtest/gtest.h>
#include "absl/status/statusor.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/profiling/memory_info.h"
namespace odml {
namespace {
class TflCalibrationUtilsTest : public ::testing::Test {
protected:
void SetUp() override {
interpreter_ = std::make_unique<tflite::Interpreter>();
}
std::unique_ptr<tflite::Interpreter> interpreter_;
};
TEST_F(TflCalibrationUtilsTest, InvokeWithCalibrationFloat) {
// 1. Add and configure 2 float32 tensors.
interpreter_->AddTensors(2);
interpreter_->SetInputs({0});
interpreter_->SetOutputs({1});
interpreter_->SetTensorParametersReadWrite(
/*tensor_index=*/0, /*type=*/kTfLiteFloat32, /*name=*/"input",
/*dims=*/{1, 4}, /*quantization=*/TfLiteQuantization());
interpreter_->SetTensorParametersReadWrite(
/*tensor_index=*/1, /*type=*/kTfLiteFloat32, /*name=*/"output",
/*dims=*/{1, 4}, /*quantization=*/TfLiteQuantization());
// 2. Prepare input data values.
const float in[] = {1.0, 2.0, -1.0, 0.0};
// 3. Register op with a dummy invoker.
// Counter must be static because TfLiteRegistration requires a C-style
// function pointer (stateless lambda), which cannot capture local variables.
static int invoke_count;
invoke_count = 0; // Reset counter before each test.
TfLiteRegistration reg = {
.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
invoke_count++;
return kTfLiteOk;
}};
ASSERT_EQ(interpreter_->AddNodeWithParameters(
/*inputs=*/{0}, /*outputs=*/{1}, /*init_data=*/nullptr,
/*init_data_size=*/0, /*builtin_data=*/nullptr,
/*registration=*/&reg),
kTfLiteOk);
// 4. Allocate the input tensors and populate data before calibration.
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
memcpy(interpreter_->tensor(0)->data.raw, in, sizeof(in));
// 5. Run calibration step and check min/max limits.
auto status_or_stats = InvokeWithCalibration(interpreter_.get(), 0);
ASSERT_TRUE(status_or_stats.ok());
auto stats = *status_or_stats;
EXPECT_EQ(invoke_count, 1);
EXPECT_TRUE(stats.count("input"));
EXPECT_EQ(stats["input"].min, -1.0f);
EXPECT_EQ(stats["input"].max, 2.0f);
}
TEST_F(TflCalibrationUtilsTest, InvokeWithCalibrationInt32) {
// 1. Add and configure 1 int32 tensor.
interpreter_->AddTensors(1);
interpreter_->SetInputs({0});
interpreter_->SetTensorParametersReadWrite(
/*tensor_index=*/0, /*type=*/kTfLiteInt32, /*name=*/"input_i32",
/*dims=*/{1, 4}, /*quantization=*/TfLiteQuantization());
// 2. Prepare int32 input data values.
const int32_t in[] = {10, 20, -10, 0};
// 3. Register op with a dummy invoker.
// Counter must be static because TfLiteRegistration requires a C-style
// function pointer (stateless lambda), which cannot capture local variables.
static int invoke_count;
invoke_count = 0; // Reset counter before each test.
TfLiteRegistration reg = {
.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
invoke_count++;
return kTfLiteOk;
}};
ASSERT_EQ(interpreter_->AddNodeWithParameters(
/*inputs=*/{0}, /*outputs=*/{}, /*init_data=*/nullptr,
/*init_data_size=*/0, /*builtin_data=*/nullptr,
/*registration=*/&reg),
kTfLiteOk);
// 4. Allocate the int32 tensors and populate data before calibration.
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
memcpy(interpreter_->tensor(0)->data.raw, in, sizeof(in));
// 5. Run calibration step and check min/max limits.
auto status_or_stats = InvokeWithCalibration(interpreter_.get(), 0);
ASSERT_TRUE(status_or_stats.ok());
auto stats = *status_or_stats;
EXPECT_EQ(invoke_count, 1);
EXPECT_TRUE(stats.count("input_i32"));
EXPECT_EQ(stats["input_i32"].min, -10.0f);
EXPECT_EQ(stats["input_i32"].max, 20.0f);
}
TEST_F(TflCalibrationUtilsTest, GetMemoryUsage) {
tflite::profiling::memory::MemoryUsage usage = GetMemoryUsage();
// We can't check exact values, but they should be filled somehow.
EXPECT_GT(usage.total_allocated_bytes, 0);
}
} // namespace
} // namespace odml
@@ -0,0 +1,92 @@
/* Copyright 2026 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 <Python.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include "pybind11/pybind11.h" // from @pybind11
#include "pybind11/pytypes.h" // from @pybind11
#include "pybind11/stl.h" // from @pybind11
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/profiling/memory_info.h"
#include "tensorflow/lite/profiling/profiler_based_calibration/tfl_calibration_utils.h"
#include "tensorflow/lite/stateful_error_reporter.h"
namespace {
namespace py = pybind11;
using tflite::profiling::memory::MemoryUsage;
} // namespace
PYBIND11_MODULE(_pywrap_tfl_calibration, m) {
py::class_<odml::Range>(m, "Range", R"pbdoc(
Min/max range for a tensor.
Attributes:
min: min value.
max: max value.
)pbdoc")
.def_readonly("min", &odml::Range::min)
.def_readonly("max", &odml::Range::max);
py::class_<MemoryUsage>(m, "MemoryUsage", R"pbdoc(
Memory usage statistics.
Attributes:
mem_footprint_kb: Max resident set size in KB.
total_allocated_bytes: Total non-mmapped heap space allocated in bytes.
in_use_allocated_bytes: Total heap bytes in use in bytes.
private_footprint_bytes: Private footprint in bytes.
)pbdoc")
.def_readonly("mem_footprint_kb", &MemoryUsage::mem_footprint_kb)
.def_readonly("total_allocated_bytes",
&MemoryUsage::total_allocated_bytes)
.def_readonly("in_use_allocated_bytes",
&MemoryUsage::in_use_allocated_bytes)
.def_readonly("private_footprint_bytes",
&MemoryUsage::private_footprint_bytes);
m.def(
"get_memory_usage",
[]() {
py::gil_scoped_release release;
return odml::GetMemoryUsage();
},
R"pbdoc(
Returns memory usage stats.
)pbdoc");
m.def(
"InvokeWithCalibration",
[](py::object interpreter_handle, int subgraph_index) {
auto* interpreter = reinterpret_cast<tflite::Interpreter*>(
interpreter_handle.cast<intptr_t>());
py::gil_scoped_release release;
auto status_or_map = odml::InvokeWithCalibration(
interpreter, subgraph_index,
static_cast<tflite::StatefulErrorReporter*>(
interpreter->error_reporter()));
if (!status_or_map.ok())
throw std::runtime_error(
std::string(status_or_map.status().message()));
return status_or_map.value();
},
R"pbdoc(
Invoke the given ``tf.lite.Interpreter`` for calibration. Assumes
input tensors are already set.
Args:
interpreter: The ``tf.lite:Interpreter`` to invoke.
subgraph_index: The subgraph index to run.
Returns:
dict: A map of tensor names to Range objects containing 'min' and 'max' values.
)pbdoc");
}
@@ -0,0 +1,104 @@
/* Copyright 2026 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/profiling/profiler_based_calibration/tfl_tensor_stats_profiler.h"
#include <cstdint>
#include <cstring>
#include <utility>
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
namespace odml {
TensorStatsProfiler::TensorStatsProfiler(const tflite::Interpreter& interpreter,
Callback callback)
: interpreter_(interpreter), callback_(callback) {}
uint32_t TensorStatsProfiler::BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) {
// Process subgraph inputs at the beginning of each subgraph invoke.
if (tag && strcmp(tag, "Invoke") == 0) {
const int64_t subgraph_idx = event_metadata2;
const tflite::Subgraph* subgraph = interpreter_.subgraph(subgraph_idx);
if (subgraph) {
for (const int input_tensor_index : subgraph->inputs()) {
if (input_tensor_index != kTfLiteOptionalTensor) {
callback_(subgraph->tensor(input_tensor_index));
}
}
} else {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"TensorStatsProfiler: subgraph %d not found.",
subgraph_idx);
}
return 0; // No event handle for input tensors.
}
// Only capture operator invoke events for intermediate/output activations.
if (event_type != EventType::OPERATOR_INVOKE_EVENT) {
return 0;
}
const int64_t node_index = event_metadata1;
const int64_t subgraph_index = event_metadata2;
// Store the subgraph and node index for later retrieval.
events_.push_back({
.subgraph_index = subgraph_index,
.node_index = node_index,
});
return events_.size();
}
void TensorStatsProfiler::EndEvent(uint32_t event_handle) {
if (!event_handle || events_.size() < event_handle) {
return;
}
// Retrieve the node from the event handle.
const auto& event = events_[event_handle - 1];
const tflite::Subgraph* subgraph =
interpreter_.subgraph(event.subgraph_index);
if (!subgraph) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"TensorStatsProfiler: subgraph %d not found.",
event.subgraph_index);
return;
}
const std::pair<TfLiteNode, TfLiteRegistration>* node_and_reg =
subgraph->node_and_registration(event.node_index);
if (!node_and_reg) {
TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING,
"TensorStatsProfiler: node %d in subgraph %d not found.",
event.node_index, event.subgraph_index);
return;
}
const TfLiteNode& node = node_and_reg->first;
// Invoke the callback for each output tensor of the operator.
for (int i = 0; i < node.outputs->size; ++i) {
const int tensor_index = node.outputs->data[i];
if (tensor_index != kTfLiteOptionalTensor) {
callback_(subgraph->tensor(tensor_index));
}
}
}
} // namespace odml
@@ -0,0 +1,70 @@
/* Copyright 2026 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_PROFILING_PROFILER_BASED_CALIBRATION_TFL_TENSOR_STATS_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_PROFILER_BASED_CALIBRATION_TFL_TENSOR_STATS_PROFILER_H_
#include <cstdint>
#include <functional>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
namespace odml {
// The TensorStatsProfiler collects stats for each non-constant tensor in a
// subgraph during interpreter invocation.
class TensorStatsProfiler : public tflite::Profiler {
public:
using Callback = std::function<void(const TfLiteTensor*)>;
TensorStatsProfiler(const tflite::Interpreter& interpreter,
Callback callback);
// This profiler may be invoked at multiple points throughout the
// execution of a subgraph. At the beginning of each subgraph invoke,
// capture the input tensor stats with the provided callback. At the beginning
// of each operator invoke, stores the subgraph and node index for later
// retrieval.
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) override;
// At the end of an operator invoke event, calculates the tensor stats for the
// operator's output tensors with the provided callback.
void EndEvent(uint32_t event_handle) override;
private:
struct EventMetadata {
int64_t subgraph_index;
int64_t node_index;
};
// A mapping between event IDs and (subgraph_index, node_index).
std::vector<EventMetadata> events_;
// A handle to the active TFLite interpreter.
const tflite::Interpreter& interpreter_;
// A user provided callback to calculate tensor stats for a given tensor. The
// callback signature is:
// void Callback(const TfLiteTensor* tensor);
Callback callback_;
};
} // namespace odml
#endif // TENSORFLOW_LITE_PROFILING_PROFILER_BASED_CALIBRATION_TFL_TENSOR_STATS_PROFILER_H_
@@ -0,0 +1,112 @@
/* Copyright 2026 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/profiling/profiler_based_calibration/tfl_tensor_stats_profiler.h"
#include <cstddef>
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
namespace odml {
namespace {
TEST(TensorStatsProfilerTest, IgnoreNotOperatorInvokeEvents) {
tflite::Interpreter interpreter;
TensorStatsProfiler profiler(interpreter, [](const TfLiteTensor*) {});
// Any event type other than OPERATOR_INVOKE_EVENT should be ignored.
EXPECT_EQ(
profiler.BeginEvent("Invoke", tflite::Profiler::EventType::DEFAULT, 0, 0),
0);
EXPECT_EQ(
profiler.BeginEvent(
"Invoke", tflite::Profiler::EventType::DELEGATE_OPERATOR_INVOKE_EVENT,
0, 0),
0);
}
TEST(TensorStatsProfilerTest, HandleOperatorInvokeEvent) {
// 1. Build a graph with 2 inputs, 1 intermediate, and 1 output tensor.
tflite::Interpreter interpreter;
interpreter.AddTensors(4);
interpreter.SetInputs({0, 1});
interpreter.SetOutputs({3});
// 2. Define dummy operator lifecycle callbacks.
TfLiteRegistration reg = {
.init = [](TfLiteContext* context, const char* buffer,
size_t length) -> void* { return nullptr; },
.free = [](TfLiteContext* context, void* buffer) {},
.prepare = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
return kTfLiteOk;
},
.invoke = [](TfLiteContext* context, TfLiteNode* node) -> TfLiteStatus {
return kTfLiteOk;
},
};
// 3. Attach Ops to sequence the tensor dependencies.
// Op 0: {0, 1} -> 2.
ASSERT_EQ(interpreter.AddNodeWithParameters(
/*inputs=*/{0, 1}, /*outputs=*/{2}, /*init_data=*/nullptr,
/*init_data_size=*/0, /*builtin_data=*/nullptr,
/*registration=*/&reg),
kTfLiteOk);
// Op 1: 2 -> 3.
ASSERT_EQ(interpreter.AddNodeWithParameters(
/*inputs=*/{2}, /*outputs=*/{3}, /*init_data=*/nullptr,
/*init_data_size=*/0, /*builtin_data=*/nullptr,
/*registration=*/&reg),
kTfLiteOk);
// 4. Allocate memory and prepare profiler to accumulate tensors.
ASSERT_EQ(interpreter.AllocateTensors(), kTfLiteOk);
std::vector<const TfLiteTensor*> captured_tensors;
TensorStatsProfiler profiler(interpreter, [&](const TfLiteTensor* t) {
captured_tensors.push_back(t);
});
// 5. Trigger sequential execution events across the operators.
// Simulate execution of subgraph invoke boundary.
const uint32_t h_subgraph =
profiler.BeginEvent("Invoke", tflite::Profiler::EventType::DEFAULT, 0, 0);
profiler.EndEvent(h_subgraph);
// Simulate execution of Op 0 (node 0).
const uint32_t h0 = profiler.BeginEvent(
"OP1", tflite::Profiler::EventType::OPERATOR_INVOKE_EVENT, 0, 0);
profiler.EndEvent(h0);
// Simulate execution of Op 1 (node 1).
const uint32_t h1 = profiler.BeginEvent(
"OP2", tflite::Profiler::EventType::OPERATOR_INVOKE_EVENT, 1, 0);
profiler.EndEvent(h1);
// 6. Verify that all input, intermediate, and output tensors are captured
// correctly.
EXPECT_EQ(captured_tensors.size(), 4);
EXPECT_EQ(captured_tensors[0], interpreter.tensor(0));
EXPECT_EQ(captured_tensors[1], interpreter.tensor(1));
EXPECT_EQ(captured_tensors[2], interpreter.tensor(2));
EXPECT_EQ(captured_tensors[3], interpreter.tensor(3));
}
} // namespace
} // namespace odml
+140
View File
@@ -0,0 +1,140 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/profiler.h"
#include <unistd.h>
#include <chrono> // NOLINT(build/c++11)
#include <cmath>
#include <thread> // NOLINT(build/c++11)
#include <gtest/gtest.h>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/buffered_profiler.h"
#include "tensorflow/lite/profiling/noop_profiler.h"
#include "tensorflow/lite/profiling/profile_buffer.h"
namespace tflite {
namespace profiling {
namespace {
double GetDurationOfEventMs(const ProfileEvent* event) {
return (event->elapsed_time) / 1e3;
}
void SleepForQuarterSecond(tflite::Profiler* profiler) {
ScopedProfile profile(profiler, "SleepForQuarter");
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
void ChildFunction(tflite::Profiler* profiler) {
ScopedProfile profile(profiler, "Child");
SleepForQuarterSecond(profiler);
}
void ParentFunction(tflite::Profiler* profiler) {
ScopedProfile profile(profiler, "Parent");
for (int i = 0; i < 2; i++) {
ChildFunction(profiler);
}
}
TEST(ProfilerTest, NoProfilesAreCollectedWhenDisabled) {
BufferedProfiler profiler(1024);
ParentFunction(&profiler);
auto profile_events = profiler.GetProfileEvents();
EXPECT_EQ(0, profile_events.size());
}
TEST(ProfilerTest, NoProfilesAreCollectedWhenEventTypeUnsupported) {
BufferedProfiler profiler(1024);
tflite::Profiler* p = &profiler;
p->AddEvent("Hello",
Profiler::EventType::GENERAL_RUNTIME_INSTRUMENTATION_EVENT,
/*elaped_time*/ 1,
/*event_metadata*/ 2);
auto handler = p->BeginEvent(
"begin", Profiler::EventType::GENERAL_RUNTIME_INSTRUMENTATION_EVENT, 0);
p->EndEvent(handler);
auto profile_events = profiler.GetProfileEvents();
EXPECT_EQ(0, profile_events.size());
}
TEST(ProfilingTest, ProfilesAreCollected) {
BufferedProfiler profiler(1024);
profiler.StartProfiling();
ParentFunction(&profiler);
profiler.StopProfiling();
auto profile_events = profiler.GetProfileEvents();
// ParentFunction calls the ChildFunction 2 times.
// Each ChildFunction calls SleepForQuarterSecond once.
// We expect 1 entry for ParentFunction, 2 for ChildFunction and 2 for
// SleepForQuarterSecond: Total: 1+ 2 + 2 = 5
// Profiles should look like:
// Parent ~ 500 ms (due to 2 Child calls)
// - Child ~ 250 ms (due to SleepForQuarter calls)
// - SleepForQuarter ~ 250ms
// - Child ~ 250 ms (due to SleepForQuarter calls)
// - SleepForQuarter ~ 250ms
//
ASSERT_EQ(5, profile_events.size());
EXPECT_EQ("Parent", profile_events[0]->tag);
EXPECT_EQ("Child", profile_events[1]->tag);
EXPECT_EQ("SleepForQuarter", profile_events[2]->tag);
EXPECT_EQ("Child", profile_events[3]->tag);
EXPECT_EQ("SleepForQuarter", profile_events[4]->tag);
#ifndef ADDRESS_SANITIZER
// ASAN build is sometimes very slow. Set a large epsilon to avoid flakiness.
// Due to flakiness, just verify relative values match.
const int eps_ms = 50;
auto parent_ms = GetDurationOfEventMs(profile_events[0]);
double child_ms[2], sleep_for_quarter_ms[2];
child_ms[0] = GetDurationOfEventMs(profile_events[1]);
child_ms[1] = GetDurationOfEventMs(profile_events[3]);
sleep_for_quarter_ms[0] = GetDurationOfEventMs(profile_events[2]);
sleep_for_quarter_ms[1] = GetDurationOfEventMs(profile_events[4]);
EXPECT_NEAR(parent_ms, child_ms[0] + child_ms[1], eps_ms);
EXPECT_NEAR(child_ms[0], sleep_for_quarter_ms[0], eps_ms);
EXPECT_NEAR(child_ms[1], sleep_for_quarter_ms[1], eps_ms);
#endif
}
TEST(ProfilingTest, NullProfiler) {
Profiler* profiler = nullptr;
{ SCOPED_TAGGED_OPERATOR_PROFILE(profiler, "noop", 1); }
}
TEST(ProfilingTest, ScopedProfile) {
BufferedProfiler profiler(1024);
profiler.StartProfiling();
{ SCOPED_TAGGED_OPERATOR_PROFILE(&profiler, "noop", 1); }
profiler.StopProfiling();
auto profile_events = profiler.GetProfileEvents();
EXPECT_EQ(1, profile_events.size());
}
TEST(ProfilingTest, NoopProfiler) {
NoopProfiler profiler;
profiler.StartProfiling();
{ SCOPED_TAGGED_OPERATOR_PROFILE(&profiler, "noop", 1); }
profiler.StopProfiling();
auto profile_events = profiler.GetProfileEvents();
EXPECT_EQ(0, profile_events.size());
}
} // namespace
} // namespace profiling
} // namespace tflite
+55
View File
@@ -0,0 +1,55 @@
load("@com_google_protobuf//bazel:proto_library.bzl", "proto_library")
load("@rules_python//python:proto.bzl", "py_proto_library")
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"],
)
proto_library(
name = "profiling_info_proto",
srcs = ["profiling_info.proto"],
compatible_with = get_compatible_with_portable(),
visibility = ["//visibility:public"],
)
proto_library(
name = "model_runtime_info_proto",
srcs = ["model_runtime_info.proto"],
compatible_with = get_compatible_with_portable(),
visibility = ["//visibility:public"],
deps = [":profiling_info_proto"],
)
tf_proto_library(
name = "profiling_info", # bzl adds _py
srcs = ["profiling_info.proto"],
visibility = ["//visibility:public"],
)
tf_proto_library(
name = "model_runtime_info", # bzl adds _py
srcs = ["model_runtime_info.proto"],
protodeps = [":profiling_info"],
visibility = ["//visibility:public"],
)
# copybara:uncomment_begin(google-only)
# py_proto_library(
# name = "profiling_info_py_pb2",
# compatible_with = get_compatible_with_portable(),
# deps = [":profiling_info_proto"],
# )
#
# py_proto_library(
# name = "model_runtime_info_py_pb2",
# compatible_with = get_compatible_with_portable(),
# deps = [":model_runtime_info_proto"],
# )
# copybara:uncomment_end
@@ -0,0 +1,57 @@
#
# Copyright 2024 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
#
# https://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.
find_package(Protobuf REQUIRED)
add_library(profiling_info_proto profiling_info.proto)
list(APPEND profiling_info_generated_files
${CMAKE_BINARY_DIR}/tensorflow/lite/profiling/proto/profiling_info.pb.cc
${CMAKE_BINARY_DIR}/tensorflow/lite/profiling/proto/profiling_info.pb.h)
# Generate profiling_info.pb.cc and profiling_info.pb.h from
# profiling_info.proto using protoc. Once the protobuf package version is
# upgraded, we can use protobuf_generate_cpp/protobuf_generate here directly.
add_custom_command(
OUTPUT ${profiling_info_generated_files}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
ARGS --cpp_out=${CMAKE_BINARY_DIR} --proto_path=${TENSORFLOW_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/profiling_info.proto
DEPENDS ${Protobuf_PROTOC_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/profiling_info.proto
)
set_source_files_properties(${profiling_info_generated_files} PROPERTIES GENERATED TRUE)
target_sources(profiling_info_proto PRIVATE ${profiling_info_generated_files})
target_link_libraries(profiling_info_proto protobuf::libprotobuf)
target_include_directories(profiling_info_proto PUBLIC ${CMAKE_BINARY_DIR})
add_library(model_runtime_info_proto model_runtime_info.proto)
list(APPEND model_runtime_info_generated_files
${CMAKE_BINARY_DIR}/tensorflow/lite/profiling/proto/model_runtime_info.pb.cc
${CMAKE_BINARY_DIR}/tensorflow/lite/profiling/proto/model_runtime_info.pb.h
)
# Generate model_runtime_info.pb.cc and model_runtime_info.pb.h from
# model_runtime_info.proto using protoc. Once the protobuf package version is
# upgraded, we can use protobuf_generate_cpp/protobuf_generate here directly.
add_custom_command(
OUTPUT ${model_runtime_info_generated_files}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
ARGS --cpp_out=${CMAKE_BINARY_DIR} --proto_path=${TENSORFLOW_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/model_runtime_info.proto
DEPENDS ${Protobuf_PROTOC_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/model_runtime_info.proto ${profiling_info_generated_files}
)
set_source_files_properties(${model_runtime_info_generated_files} PROPERTIES GENERATED TRUE)
target_sources(model_runtime_info_proto PRIVATE ${model_runtime_info_generated_files})
target_link_libraries(model_runtime_info_proto protobuf::libprotobuf)
target_include_directories(model_runtime_info_proto PUBLIC ${CMAKE_BINARY_DIR})
@@ -0,0 +1,159 @@
/* Copyright 2024 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 optional 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 tflite.profiling;
import "tensorflow/lite/profiling/proto/profiling_info.proto";
option java_package = "tflite.profiling";
option java_multiple_files = true;
// Corresponds to a TFLite Model.
message ModelRuntimeDetails {
optional string model_name = 1;
repeated RuntimeSubgraph subgraphs = 2;
}
message RuntimeSubgraph {
enum SubgraphType {
UNKNOWN_SUBGRAPH = 0;
// Set if the RuntimeSubgraph is from the TFLite runtime.
TFLITE_SUBGRAPH = 1;
// Set if the RuntimeSubgraph is from a delegate's runtime.
DELEGATE_SUBGRAPH = 2;
}
optional int32 subgraph_id = 1;
repeated Edge edges = 2; // All the edges that are part of this subgraph.
repeated Node nodes = 3; // All the nodes that are part of this subgraph.
// List of node ids in the execution plan
repeated int32 execution_plan = 4 [packed = true];
optional SubgraphType subgraph_type = 5;
// The name of the subgraph.
optional string name = 6;
}
message Node {
optional int32 id = 1;
optional string name = 2;
optional string type = 3;
// Inputs and outputs are ids of the edges specified in the corresponding
// subgraph.
repeated int32 inputs = 4 [packed = true];
repeated int32 outputs = 5 [packed = true];
// Intermediate tensors to this node expressed as indices into the
// subgraph's tensors.
repeated int32 intermediates = 6 [packed = true];
// Indices of temporary tensors used during the computations. This usually
// contains no tensors, but ops are allowed to change that if they need
// scratch space of any sort.
repeated int32 temporaries = 7 [packed = true];
optional OpProfileData op_profile_data = 10;
oneof node_info {
// If this node is a delegate node, metadata about it.
DelegateNodeDetails delegate_node_details = 8;
// If this node is a tflite node and delegated, the delegate node
// id.
int32 delegated_to_node_id = 9;
}
}
message DelegateNodeDetails {
// Delegate name, e.g., TfLiteXNNPackDelegate, TfLiteGpuDelegateV2, etc.
// This comes from the custom_name field in the TfLiteRegistration struct.
optional string delegate_name = 1;
// These node ids correspond to the nodes saved in the upper-level
// RunTimeSubgraph.
repeated int32 tflite_node_ids_replaced = 2 [packed = true];
}
message Edge {
// LINT.IfChange(EdgeDataType)
enum DataType {
// Similar to TFLiteType.
UNKNOWN_TYPE = 0;
FLOAT32 = 1;
INT32 = 2;
UINT8 = 3;
INT64 = 4;
STRING = 5;
BOOL = 6;
INT16 = 7;
COMPLEX64 = 8;
INT8 = 9;
FLOAT16 = 10;
FLOAT64 = 11;
COMPLEX128 = 12;
UINT64 = 13;
RESOURCE = 14;
VARIANT = 15;
UINT32 = 16;
UINT16 = 17;
INT4 = 18;
BFLOAT16 = 19;
INT2 = 20;
UINT4 = 21;
FLOAT8_E4M3FN = 22;
FLOAT8_E5M2 = 23;
}
// LINT.ThenChange(//tensorflow/lite/profiling/model_runtime_info.cc:EdgeDataTypeTransform)
enum LayoutType {
UNKNOWN = 0;
SCALAR = 1;
LINEAR = 2;
HW = 3;
CHW = 4;
HWC = 5;
OIHW = 6;
OHWI = 7;
IHWO = 8;
IOHW = 9;
BHWC = 10;
HWDC = 11;
BHWDC = 12;
HWD = 13;
OHWDI = 14;
HWIO = 15;
}
optional int32 id = 1;
optional string name = 2;
optional DataType data_type = 3;
repeated int32 shape = 4 [packed = true];
// This corresponds to the memory allocation type of the tensor in the TFLite
// runtime. Maps to the TfLiteAllocationType enum.
optional string allocation_type = 5;
// What layout this tensor is stored in.
optional LayoutType layout_type = 6;
optional int32 size = 7;
}
@@ -0,0 +1,72 @@
/* Copyright 2024 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 optional 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 tflite.profiling;
option java_package = "tflite.profiling";
option java_multiple_files = true;
message BenchmarkProfilingData {
optional string model_name = 1;
optional ModelProfilingData init_profile = 2;
optional ModelProfilingData runtime_profile = 3;
}
// LINT.IfChange(ModelProfilingData)
message ModelProfilingData {
repeated SubGraphProfilingData subgraph_profiles = 1;
repeated DelegateProfilingData delegate_profiles = 2;
}
// LINT.ThenChange(//tensorflow/lite/profiling/profile_summary_formatter_test.cc:ModelProfilingDataComparator)
// LINT.IfChange(SubGraphProfilingData)
message SubGraphProfilingData {
optional string subgraph_name = 1;
optional int32 subgraph_index = 2;
repeated OpProfileData per_op_profiles = 3;
}
// LINT.ThenChange(//tensorflow/lite/profiling/profile_summary_formatter_test.cc:SubGraphProfilingDataComparator)
message DelegateProfilingData {
optional string delegate_name = 1;
repeated OpProfileData per_op_profiles = 2;
}
// LINT.IfChange(OpProfilingStat)
message OpProfilingStat {
optional int64 first = 1;
optional int64 last = 2;
optional int64 avg = 3;
optional float stddev = 4;
optional float variance = 5;
optional int64 min = 6;
optional int64 max = 7;
optional int64 sum = 8;
optional int64 count = 9;
}
// LINT.ThenChange(//tensorflow/lite/profiling/profile_summary_formatter_test.cc:OpProfilingStatComparator)
// LINT.IfChange(OpProfileData)
message OpProfileData {
optional string node_type = 1;
optional OpProfilingStat inference_microseconds = 2;
optional OpProfilingStat mem_kb = 3;
optional int64 times_called = 4;
optional string name = 5;
optional int64 run_order = 6;
}
// LINT.ThenChange(//tensorflow/lite/profiling/profile_summary_formatter_test.cc:OpProfileDataComparator)
+111
View File
@@ -0,0 +1,111 @@
/* Copyright 2022 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/profiling/root_profiler.h"
#include <memory>
#include <utility>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
void RootProfiler::AddProfiler(Profiler* profiler) {
if (profiler == nullptr) return;
profilers_.push_back(profiler);
}
void RootProfiler::AddProfiler(std::unique_ptr<Profiler>&& profiler) {
if (profiler == nullptr) return;
owned_profilers_.emplace_back(std::move(profiler));
profilers_.push_back(owned_profilers_.back().get());
}
uint32_t RootProfiler::BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) {
// TODO(b/238913100): Remove the workaround.
if (profilers_.size() == 1) {
return profilers_[0]->BeginEvent(tag, event_type, event_metadata1,
event_metadata2);
}
auto id = next_event_id_++;
std::vector<uint32_t> event_ids;
event_ids.reserve(profilers_.size());
for (auto* profiler : profilers_) {
event_ids.push_back(profiler->BeginEvent(tag, event_type, event_metadata1,
event_metadata2));
}
events_.emplace(id, std::move(event_ids));
return id;
}
void RootProfiler::EndEvent(uint32_t event_handle, int64_t event_metadata1,
int64_t event_metadata2) {
// TODO(b/238913100): Remove the workaround.
if (profilers_.size() == 1) {
return profilers_[0]->EndEvent(event_handle, event_metadata1,
event_metadata2);
}
if (const auto it = events_.find(event_handle); it != events_.end()) {
const auto& event_ids = it->second;
for (auto idx = 0; idx < event_ids.size(); idx++) {
profilers_[idx]->EndEvent(event_ids[idx], event_metadata1,
event_metadata2);
}
events_.erase(it);
}
}
void RootProfiler::EndEvent(uint32_t event_handle) {
// TODO(b/238913100): Remove the workaround.
if (profilers_.size() == 1) {
return profilers_[0]->EndEvent(event_handle);
}
if (const auto it = events_.find(event_handle); it != events_.end()) {
const auto& event_ids = it->second;
for (auto idx = 0; idx < event_ids.size(); idx++) {
profilers_[idx]->EndEvent(event_ids[idx]);
}
events_.erase(it);
}
}
void RootProfiler::AddEvent(const char* tag, EventType event_type,
uint64_t metric, int64_t event_metadata1,
int64_t event_metadata2) {
for (auto* profiler : profilers_) {
profiler->AddEvent(tag, event_type, metric, event_metadata1,
event_metadata2);
}
}
void RootProfiler::AddEventWithData(const char* tag, EventType event_type,
const void* data) {
for (auto* profiler : profilers_) {
profiler->AddEventWithData(tag, event_type, data);
}
}
void RootProfiler::RemoveChildProfilers() {
owned_profilers_.clear();
profilers_.clear();
// Previous `BeginEvent` calls will be discarded.
events_.clear();
}
} // namespace profiling
} // namespace tflite
+105
View File
@@ -0,0 +1,105 @@
/* Copyright 2022 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_PROFILING_ROOT_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_ROOT_PROFILER_H_
#include <cstdint>
#include <map>
#include <memory>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
/// A root profiler instance installed in TFLite runtime.
/// It's capable to dispatching profiling events to all child profilers attached
/// to it. Child profilers can either accept for discard the events based on the
/// event type.
class RootProfiler : public Profiler {
public:
RootProfiler() = default;
~RootProfiler() override = default;
// Not copiable.
RootProfiler(const RootProfiler&) = delete;
RootProfiler& operator=(const RootProfiler&) = delete;
// Movable.
RootProfiler(RootProfiler&&) = default;
RootProfiler& operator=(RootProfiler&&) = default;
/// Adds a profiler to root profiler.
/// Added `profiler` should not be nullptr or it will be ignored.
/// Caller must retains the ownership. The lifetime should exceed the
/// lifetime of the RootProfiler.
void AddProfiler(Profiler* profiler);
/// Adds a profiler to RootProfiler.
/// Added `profiler` should not be nullptr or it will be ignored.
/// Transfers the ownership of `profiler` to RootProfiler.
void AddProfiler(std::unique_ptr<Profiler>&& profiler);
/// Signals the beginning of an event to all child profilers.
/// The `tag`, `event_metadata1` and `event_metadata2` arguments have
/// different interpretations based on the actual Profiler instance
/// and the `event_type`.
/// Returns a handle to the profile event which can be used in a later
/// `EndEvent` call.
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) override;
/// Signals an end to the specified profile event to all child profilers with
/// 'event_metadata's.
/// An invalid event handle (e.g. not a value returned from BeginEvent call or
/// a handle invalidated by RemoveChildProfilers) will be ignored.
void EndEvent(uint32_t event_handle, int64_t event_metadata1,
int64_t event_metadata2) override;
/// Signals an end to the specified profile event to all child profilers.
/// An invalid event handle (e.g. not a value returned from BeginEvent call or
/// a handle invalidated by RemoveChildProfilers) will be ignored.
void EndEvent(uint32_t event_handle) override;
/// Appends an event of type 'event_type' with 'tag' and 'event_metadata'
/// The `tag`, `metric`, `event_metadata1` and `event_metadata2` arguments
/// have different interpretations based on the actual Profiler instance and
/// the `event_type`.
void AddEvent(const char* tag, EventType event_type, uint64_t metric,
int64_t event_metadata1, int64_t event_metadata2) override;
// Adds a profiler event with data.
// Data will be a const TelemetrySettings* for TELEMETRY_REPORT_SETTINGS
// and TELEMETRY_DELEGATE_REPORT_SETTINGS.
void AddEventWithData(const char* tag, EventType event_type,
const void* data) override;
/// Removes all child profilers and releases the child profiler if it's owned
/// by the root profiler. Also invalidates all event handles generated
/// from previous `BeginEvent` calls.
void RemoveChildProfilers();
private:
uint32_t next_event_id_ = 1;
std::vector<std::unique_ptr<Profiler>> owned_profilers_;
std::vector<Profiler*> profilers_;
std::map<uint32_t, std::vector<uint32_t>> events_;
};
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_ROOT_PROFILER_H_
@@ -0,0 +1,122 @@
/* Copyright 2022 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/profiling/root_profiler.h"
#include <memory>
#include <utility>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/api/profiler.h"
using ::testing::_;
using ::testing::StrictMock;
namespace tflite {
namespace profiling {
namespace {
constexpr char kTag[] = "tag";
class MockProfiler : public Profiler {
public:
MOCK_METHOD(uint32_t, BeginEvent,
(const char* tag, EventType event_type, int64_t event_metadata1,
int64_t event_metadata2),
(override));
MOCK_METHOD(void, EndEvent, (uint32_t event_handle), (override));
MOCK_METHOD(void, EndEvent,
(uint32_t event_handle, int64_t event_metadata1,
int64_t event_metadata2),
(override));
MOCK_METHOD(void, AddEvent,
(const char* tag, EventType event_type, uint64_t metric,
int64_t event_metadata1, int64_t event_metadata2),
(override));
MOCK_METHOD(void, AddEventWithData,
(const char* tag, EventType event_type, const void* data),
(override));
};
using MockProfilerT = StrictMock<MockProfiler>;
TEST(RootProfilerTest, ChildProfilerTest) {
auto mock_profiler = std::make_unique<MockProfilerT>();
auto* mock = mock_profiler.get();
RootProfiler root;
root.AddProfiler(mock_profiler.get());
ON_CALL(*mock, BeginEvent(_, _, _, _)).WillByDefault(testing::Return(42));
EXPECT_CALL(*mock, BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2));
EXPECT_CALL(*mock, EndEvent(42, 3, 4));
EXPECT_CALL(*mock, AddEvent(kTag, Profiler::EventType::OPERATOR_INVOKE_EVENT,
5, 6, 7));
EXPECT_CALL(*mock, AddEventWithData(kTag, Profiler::EventType::DEFAULT, _));
// Calls each method sequentially.
auto begin = root.BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2);
root.EndEvent(begin, 3, 4);
root.AddEvent(kTag, Profiler::EventType::OPERATOR_INVOKE_EVENT, 5, 6, 7);
root.AddEventWithData(kTag, Profiler::EventType::DEFAULT, nullptr);
}
TEST(RootProfilerTest, OwnedProfilerTest) {
auto mock_profiler = std::make_unique<MockProfilerT>();
auto* mock = mock_profiler.get();
RootProfiler root;
root.AddProfiler(std::move(mock_profiler));
ON_CALL(*mock, BeginEvent(_, _, _, _)).WillByDefault(testing::Return(42));
EXPECT_CALL(*mock, BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2));
EXPECT_CALL(*mock, EndEvent(42));
EXPECT_CALL(*mock, AddEvent(kTag, Profiler::EventType::OPERATOR_INVOKE_EVENT,
3, 4, 5));
// Calls each method sequentially.
auto begin = root.BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2);
root.EndEvent(begin);
root.AddEvent(kTag, Profiler::EventType::OPERATOR_INVOKE_EVENT, 3, 4, 5);
}
TEST(RootProfilerTest, MultipleProfilerTest) {
auto mock_profiler0 = std::make_unique<MockProfilerT>();
auto* mock0 = mock_profiler0.get();
auto mock_profiler1 = std::make_unique<MockProfilerT>();
auto* mock1 = mock_profiler1.get();
RootProfiler root;
root.AddProfiler(std::move(mock_profiler0));
root.AddProfiler(std::move(mock_profiler1));
// Different child profilers might return different event id.
ON_CALL(*mock0, BeginEvent(_, _, _, _)).WillByDefault(testing::Return(42));
ON_CALL(*mock1, BeginEvent(_, _, _, _)).WillByDefault(testing::Return(24));
EXPECT_CALL(*mock0, BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2));
EXPECT_CALL(*mock0, EndEvent(42));
EXPECT_CALL(*mock1, BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2));
EXPECT_CALL(*mock1, EndEvent(24));
// Calls each method sequentially.
auto begin = root.BeginEvent(kTag, Profiler::EventType::DEFAULT, 1, 2);
root.EndEvent(begin);
}
} // namespace
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,34 @@
/* Copyright 2020 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_PROFILING_SIGNPOST_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_SIGNPOST_PROFILER_H_
#include <memory>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
// Creates a platform profiler for iOS, macOS, tvOS and watchOS.
// This profiler uses Apple's signpost API for tracing events.
// User needs to set an enrionment variable 'debug.tflite.trace' for profile
// scheme at Xcode to enable this profiler.
std::unique_ptr<tflite::Profiler> MaybeCreateSignpostProfiler();
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_SIGNPOST_PROFILER_H_
@@ -0,0 +1,141 @@
/* Copyright 2020 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/profiling/signpost_profiler.h"
#import <Foundation/Foundation.h>
#import <os/log.h>
#import <os/signpost.h>
#include <cstdint>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite {
namespace profiling {
class SignpostProfiler : public tflite::Profiler {
public:
SignpostProfiler()
: log_(nullptr), msg_buf_(std::ios::out | std::ios::ate), last_event_handle_(0) {
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) {
log_ = os_log_create("org.tensorflow.lite", "Tracing");
}
}
~SignpostProfiler() override {
if (log_) {
os_release(log_);
}
}
uint32_t BeginEvent(const char *tag, EventType event_type, int64_t event_metadata1,
int64_t event_metadata2) override {
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) {
if (!os_signpost_enabled(log_)) {
return 0;
}
// We encode the signpost message as tag@event_metadata1/event_metadata2.
// In case of OPERATOR_INVOKE_EVENT, the event message will be
// op_name@node_index/subgraph_index. See the macro TFLITE_SCOPED_TAGGED_OPERATOR_PROFILE
// defined in tensorflow/lite/core/api/profiler.h for details.
msg_buf_.str(""); // reset the buffer.
msg_buf_ << tag << "@" << event_metadata1 << "/" << event_metadata2;
std::string msg_str = msg_buf_.str();
const char *msg = msg_str.c_str();
os_signpost_id_t signpost_id = os_signpost_id_generate(log_);
switch (event_type) {
case EventType::DEFAULT:
os_signpost_interval_begin(log_, signpost_id, "default", "%s", msg);
break;
case EventType::OPERATOR_INVOKE_EVENT:
os_signpost_interval_begin(log_, signpost_id, "operator invoke", "%s", msg);
break;
case EventType::DELEGATE_OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_PROFILED_OPERATOR_INVOKE_EVENT:
os_signpost_interval_begin(log_, signpost_id, "delegate operator invoke", "%s", msg);
break;
case EventType::GENERAL_RUNTIME_INSTRUMENTATION_EVENT:
os_signpost_interval_begin(log_, signpost_id, "runtime instrumentation", "%s", msg);
break;
default:
os_signpost_interval_begin(log_, signpost_id, "unknown", "%s", msg);
}
uint32_t event_handle = ++last_event_handle_;
saved_events_[event_handle] = std::make_pair(signpost_id, event_type);
return event_handle;
} else {
return 0;
}
}
void EndEvent(uint32_t event_handle) override {
if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) {
if (!os_signpost_enabled(log_)) {
return;
}
auto it = saved_events_.find(event_handle);
if (it != saved_events_.end()) {
auto signpost_id = it->second.first;
auto event_type = it->second.second;
switch (event_type) {
case EventType::DEFAULT:
os_signpost_interval_end(log_, signpost_id, "default");
break;
case EventType::OPERATOR_INVOKE_EVENT:
os_signpost_interval_end(log_, signpost_id, "operator invoke");
break;
case EventType::DELEGATE_OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_PROFILED_OPERATOR_INVOKE_EVENT:
os_signpost_interval_end(log_, signpost_id, "delegate operator invoke");
break;
case EventType::GENERAL_RUNTIME_INSTRUMENTATION_EVENT:
os_signpost_interval_end(log_, signpost_id, "runtime instrumentation");
break;
default:
os_signpost_interval_end(log_, signpost_id, "unknown");
}
saved_events_.erase(it);
}
}
}
private:
os_log_t log_;
std::stringstream msg_buf_;
uint32_t last_event_handle_;
std::unordered_map<uint32_t, std::pair<os_signpost_id_t, EventType>> saved_events_;
};
std::unique_ptr<tflite::Profiler> MaybeCreateSignpostProfiler() {
#if defined(TFLITE_ENABLE_DEFAULT_PROFILER)
return std::unique_ptr<tflite::Profiler>(new SignpostProfiler());
#else // TFLITE_ENABLE_DEFAULT_PROFILER
if ([[[NSProcessInfo processInfo] environment] objectForKey:@"debug.tflite.trace"]) {
return std::unique_ptr<tflite::Profiler>(new SignpostProfiler());
} else {
return nullptr;
}
#endif // TFLITE_ENABLE_DEFAULT_PROFILER
}
} // namespace profiling
} // namespace tflite
@@ -0,0 +1,58 @@
/* Copyright 2023 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/profiling/subgraph_tensor_profiler.h"
#include <cstring>
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/interpreter.h"
namespace tflite::profiling {
SubgraphTensorProfiler::SubgraphTensorProfiler(const Interpreter& interpreter,
CallbackT callback)
: interpreter_(interpreter), callback_(callback) {
events_.reserve(interpreter.subgraphs_size());
}
// This Subgraph aware profiler may be invoked at multiple points throughout the
// execution of a subgraph. Only handle subgraph Invoke tagged events.
uint32_t SubgraphTensorProfiler::BeginEvent(const char* tag,
EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) {
// Only listen to the "Invoke" event triggered by Subgraph::InvokeImpl().
if (strcmp(tag, "Invoke")) {
return 0;
}
events_.push_back(/*subgraph_index=*/event_metadata2);
return events_.size();
}
// Store tensors used during the subgraph's Invoke event for later retrieval.
void SubgraphTensorProfiler::EndEvent(uint32_t event_handle) {
if (!event_handle || events_.size() < event_handle) {
return;
}
const Subgraph* subgraph = interpreter_.subgraph(events_[event_handle - 1]);
for (int i = 0; i < subgraph->tensors_size(); ++i) {
callback_(subgraph->tensor(i));
}
}
} // namespace tflite::profiling
@@ -0,0 +1,58 @@
/* Copyright 2023 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_PROFILING_SUBGRAPH_TENSOR_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_SUBGRAPH_TENSOR_PROFILER_H_
#include <functional>
#include <vector>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
namespace tflite::profiling {
// The SubgraphTensorProfiler is invoked for every tensor in a subgraph at the
// end of the subgraph's execution. This profiler is constructed with a user
// provided callback to run on each tensor in the subgraph.
class SubgraphTensorProfiler : public tflite::Profiler {
public:
using CallbackT = std::function<void(const TfLiteTensor*)>;
SubgraphTensorProfiler(const Interpreter& interpreter, CallbackT callback);
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) override;
void EndEvent(uint32_t event_handle) override;
private:
// A mapping between event IDs and the subgraph that owns the event ID.
std::vector<int64_t> events_;
// A handle to the active TFLite interpreter.
const Interpreter& interpreter_;
// A user provided callback to run on each tensor in the subgraph. The
// callback signature is:
//
// void Callback(const TfLiteTensor* tensor);
CallbackT callback_;
};
} // namespace tflite::profiling
#endif // TENSORFLOW_LITE_PROFILING_SUBGRAPH_TENSOR_PROFILER_H_
@@ -0,0 +1,149 @@
/* Copyright 2023 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/profiling/subgraph_tensor_profiler.h"
#include <functional>
#include <string>
#include <unordered_set>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/subgraph.h"
#include "tensorflow/lite/kernels/subgraph_test_util.h"
namespace tflite::profiling {
namespace {
using ::testing::IsSupersetOf;
using ::testing::Not;
constexpr const char* kIfSubgraphTensorNames[] = {
"if_cond",
"if_input2",
"if_input3",
"if_output1",
};
constexpr const char* kAddSubgraphTensorNames[] = {
"add_input1",
"add_input2",
"add_output1",
};
constexpr const char* kMulSubgraphTensorNames[] = {
"mul_input1",
"mul_input2",
"mul_output1",
};
// A functor which captures all tensor names to a set for later consumption.
struct TensorGatherer {
void operator()(const TfLiteTensor* tensor) { tensors.insert(tensor->name); }
std::unordered_set<std::string> tensors;
};
// A simple test that performs `ADD` if condition is true, and `MUL` otherwise.
// The computation is: `cond ? a + b : a * b`.
class SubgraphTensorProfilerTest
: public subgraph_test_util::ControlFlowOpTest {
protected:
void SetUp() override {
AddSubgraphs(2);
builder_->BuildAddSubgraph(interpreter_->subgraph(1));
builder_->BuildMulSubgraph(interpreter_->subgraph(2));
builder_->BuildIfSubgraph(&interpreter_->primary_subgraph());
interpreter_->ResizeInputTensor(interpreter_->inputs()[0], {1});
interpreter_->ResizeInputTensor(interpreter_->inputs()[1], {2});
interpreter_->ResizeInputTensor(interpreter_->inputs()[2], {1, 2});
ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[1]), {5, 7});
subgraph_test_util::FillIntTensor(
interpreter_->tensor(interpreter_->inputs()[2]), {1, 2});
NameTensors();
}
private:
// Assign a non-null name to all tensors in every subgraph such that they can
// be uniquely identified later on.
void NameTensors() {
auto set_names = [](Subgraph* subgraph, auto names) {
for (int j = 0; j < subgraph->tensors_size(); ++j) {
subgraph->tensor(j)->name = names[j];
}
};
set_names(interpreter_->subgraph(0), kIfSubgraphTensorNames);
set_names(interpreter_->subgraph(1), kAddSubgraphTensorNames);
set_names(interpreter_->subgraph(2), kMulSubgraphTensorNames);
}
};
TEST_F(SubgraphTensorProfilerTest, TestMulSubgraph) {
TensorGatherer tensor_gatherer;
tflite::profiling::SubgraphTensorProfiler profiler(*interpreter_,
std::ref(tensor_gatherer));
interpreter_->AddProfiler(&profiler);
interpreter_->typed_input_tensor<bool>(0)[0] = false;
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
// Ensure that only subgraphs that were invoked by the interpreter had their
// tensors captured.
EXPECT_THAT(tensor_gatherer.tensors, IsSupersetOf(kIfSubgraphTensorNames));
EXPECT_THAT(tensor_gatherer.tensors, IsSupersetOf(kMulSubgraphTensorNames));
EXPECT_THAT(tensor_gatherer.tensors,
Not(IsSupersetOf(kAddSubgraphTensorNames)));
}
TEST_F(SubgraphTensorProfilerTest, TestAddSubgraph) {
TensorGatherer tensor_gatherer;
tflite::profiling::SubgraphTensorProfiler profiler(*interpreter_,
std::ref(tensor_gatherer));
interpreter_->AddProfiler(&profiler);
interpreter_->typed_input_tensor<bool>(0)[0] = true;
ASSERT_EQ(interpreter_->Invoke(), kTfLiteOk);
// Ensure that only subgraphs that were invoked by the interpreter had their
// tensors captured.
EXPECT_THAT(tensor_gatherer.tensors, IsSupersetOf(kIfSubgraphTensorNames));
EXPECT_THAT(tensor_gatherer.tensors, IsSupersetOf(kAddSubgraphTensorNames));
EXPECT_THAT(tensor_gatherer.tensors,
Not(IsSupersetOf(kMulSubgraphTensorNames)));
}
TEST_F(SubgraphTensorProfilerTest, TestBeginEvent) {
TensorGatherer tensor_gatherer;
tflite::profiling::SubgraphTensorProfiler profiler(*interpreter_,
std::ref(tensor_gatherer));
const int subgraph_id = 1;
uint32_t valid_event = profiler.BeginEvent(
"Invoke", Profiler::EventType::DEFAULT, 0, subgraph_id);
EXPECT_EQ(valid_event, 1);
uint32_t invalid_event = profiler.BeginEvent(
"NotInvoke", Profiler::EventType::DEFAULT, 0, subgraph_id);
EXPECT_EQ(invalid_event, 0);
}
} // namespace
} // namespace tflite::profiling
+75
View File
@@ -0,0 +1,75 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load("//tensorflow/lite:build_def.bzl", "tflite_copts")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
cc_library(
name = "profiler",
srcs = ["profiler.cc"],
hdrs = ["profiler.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = [
":telemetry_status",
"//tensorflow/lite/core/api",
"//tensorflow/lite/profiling/telemetry/c:profiler",
"//tensorflow/lite/profiling/telemetry/c:telemetry_setting",
],
)
cc_library(
name = "telemetry",
srcs = ["telemetry.cc"],
hdrs = ["telemetry.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = [
":telemetry_status",
"//tensorflow/lite/core/api",
"//tensorflow/lite/core/c:c_api_types",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/profiling/telemetry/c:telemetry_setting",
],
)
cc_test(
name = "telemetry_test",
srcs = ["telemetry_test.cc"],
deps = [
":profiler",
":telemetry",
":telemetry_status",
"//tensorflow/lite/core/c:c_api_types",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/profiling/telemetry/c:telemetry_setting_internal",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "profiler_test",
srcs = ["profiler_test.cc"],
deps = [
":profiler",
":telemetry",
":telemetry_status",
"//tensorflow/lite/core/c:c_api_types",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/profiling/telemetry/c:telemetry_setting",
"@com_google_googletest//:gtest_main",
],
)
cc_library(
name = "telemetry_status",
hdrs = ["telemetry_status.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = ["//tensorflow/lite/core/c:c_api_types"],
)
@@ -0,0 +1,51 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load(
"//tensorflow/lite:build_def.bzl",
"tflite_cc_library_with_c_headers_test",
"tflite_copts",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
exports_files(
srcs = [
"profiler.h",
"telemetry_setting.h",
],
visibility = [
"//tensorflow/lite:__subpackages__",
],
)
tflite_cc_library_with_c_headers_test(
name = "profiler",
hdrs = ["profiler.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = [":telemetry_setting"],
)
tflite_cc_library_with_c_headers_test(
name = "telemetry_setting",
hdrs = ["telemetry_setting.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = [
":telemetry_setting_internal",
"//tensorflow/lite/core/c:common",
],
)
cc_library(
name = "telemetry_setting_internal",
srcs = ["telemetry_setting_internal.cc"],
hdrs = ["telemetry_setting_internal.h"],
compatible_with = get_compatible_with_portable(),
copts = tflite_copts(),
deps = ["//tensorflow/lite/core/c:common"],
)
@@ -0,0 +1,85 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_C_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_C_PROFILER_H_
#include <stdint.h>
#include "tensorflow/lite/profiling/telemetry/c/telemetry_setting.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// C API for TFLite telemetry profiler.
// See C++ interface in tflite::telemetry::TelemetryProfiler.
// Note: This struct does not comply with ABI stability.
typedef struct TfLiteTelemetryProfilerStruct {
// Data that profiler needs to identify itself. This data is owned by the
// profiler. The profiler is owned in the user code, so the profiler is
// responsible for deallocating this when it is destroyed.
void* data;
// Reports a telemetry event with status.
// `event_name` indicates the name of the event (e.g. "Invoke") and should not
// be nullptr.
// `status`: uint64_t representation of TelemetryStatusCode.
void (*ReportTelemetryEvent)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, const char* event_name,
uint64_t status);
// Reports an op telemetry event with status.
// Same as `ReportTelemetryEvent`, with additional args `op_idx` and
// `subgraph_idx`.
// `status`: uint64_t representation of TelemetryStatusCode.
void (*ReportTelemetryOpEvent)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, const char* event_name,
int64_t op_idx, int64_t subgraph_idx, uint64_t status);
// Reports the model and interpreter settings.
// `setting_name` indicates the name of the setting and should not be nullptr.
// `settings`'s lifespan is not guaranteed outside the scope of
// `ReportSettings` call.
void (*ReportSettings)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, const char* setting_name,
const TfLiteTelemetrySettings* settings);
// Signals the beginning of an operator invocation.
// `op_name` is the name of the operator and should not be nullptr.
// Op invoke event are triggered with OPERATOR_INVOKE_EVENT type for TfLite
// ops and delegate kernels, and DELEGATE_OPERATOR_INVOKE_EVENT for delegate
// ops within a delegate kernels, if the instrumentation is in place.
// Returns event handle which can be passed to `EndOpInvokeEvent` later.
uint32_t (*ReportBeginOpInvokeEvent)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, const char* op_name,
int64_t op_idx, int64_t subgraph_idx);
// Signals the end to the event specified by `event_handle`.
void (*ReportEndOpInvokeEvent)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, uint32_t event_handle);
// For op / delegate op with built-in performance measurements, they
// are able to report the elapsed time directly.
// `elapsed_time` is in microsecond.
void (*ReportOpInvokeEvent)( // NOLINT
struct TfLiteTelemetryProfilerStruct* profiler, const char* op_name,
uint64_t elapsed_time, int64_t op_idx, int64_t subgraph_idx);
} TfLiteTelemetryProfilerStruct;
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_C_PROFILER_H_
@@ -0,0 +1,91 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_H_
#include <stddef.h>
#include <stdint.h>
#include "tensorflow/lite/core/c/common.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// TFLite model, interpreter or delegate settings that will be reported by
// telemetry.
// Note: This struct does not comply with ABI stability.
typedef struct TfLiteTelemetrySettings {
// Source of the settings. Determines how `data` is interpreted.
// See tflite::telemetry::TelemetrySource for definition.
uint32_t source;
// Settings data. Interpretation based on `source`.
// If `source` is TFLITE_INTERPRETER, the type of `data` will
// be `TelemetryInterpreterSettings`.
// Otherwise, the data is provided by the individual delegate.
// Owned by the caller that exports TelemetrySettings (e.g. Interpreter).
const void* data;
} TfLiteTelemetrySettings;
typedef struct TfLiteTelemetryConversionMetadata
TfLiteTelemetryConversionMetadata;
const int32_t* TfLiteTelemetryConversionMetadataGetModelOptimizationModes(
const TfLiteTelemetryConversionMetadata* metadata);
size_t TfLiteTelemetryConversionMetadataGetNumModelOptimizationModes(
const TfLiteTelemetryConversionMetadata* metadata);
// TfLite model information and settings of the interpreter.
// Note: This struct does not comply with ABI stability.
typedef struct TfLiteTelemetryInterpreterSettings
TfLiteTelemetryInterpreterSettings;
const TfLiteTelemetryConversionMetadata*
TfLiteTelemetryInterpreterSettingsGetConversionMetadata(
const TfLiteTelemetryInterpreterSettings* settings);
// Telemetry data for a specific TFLite subgraph.
typedef struct TfLiteTelemetrySubgraphInfo TfLiteTelemetrySubgraphInfo;
size_t TfLiteTelemetryInterpreterSettingsGetNumSubgraphInfo(
const TfLiteTelemetryInterpreterSettings* settings);
const TfLiteTelemetrySubgraphInfo*
TfLiteTelemetryInterpreterSettingsGetSubgraphInfo(
const TfLiteTelemetryInterpreterSettings* settings);
size_t TfLiteTelemetrySubgraphInfoGetNumQuantizations(
TfLiteTelemetrySubgraphInfo* subgraph_info);
const TfLiteQuantization* TfLiteTelemetrySubgraphInfoGetQuantizations(
TfLiteTelemetrySubgraphInfo* subgraph_info);
// Telemetry information for GPU delegate.
typedef struct TfLiteTelemetryGpuDelegateSettings
TfLiteTelemetryGpuDelegateSettings;
size_t TfLiteTelemetryGpuDelegateSettingsGetNumNodesDelegated(
const TfLiteTelemetryGpuDelegateSettings* settings);
int TfLiteTelemetryGpuDelegateSettingsGetBackend(
const TfLiteTelemetryGpuDelegateSettings* settings);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_H_
@@ -0,0 +1,78 @@
/* Copyright 2022 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/profiling/telemetry/c/telemetry_setting_internal.h"
#include <cstddef>
#include <cstdint>
extern "C" {
const TfLiteTelemetryConversionMetadata*
TfLiteTelemetryInterpreterSettingsGetConversionMetadata(
const TfLiteTelemetryInterpreterSettings* settings) {
if (settings == nullptr) return nullptr;
return settings->conversion_metadata.get();
}
const int32_t* TfLiteTelemetryConversionMetadataGetModelOptimizationModes(
const TfLiteTelemetryConversionMetadata* metadata) {
if (metadata == nullptr) return nullptr;
return metadata->model_optimization_modes.data();
}
size_t TfLiteTelemetryConversionMetadataGetNumModelOptimizationModes(
const TfLiteTelemetryConversionMetadata* metadata) {
if (metadata == nullptr) return 0;
return metadata->model_optimization_modes.size();
}
size_t TfLiteTelemetryInterpreterSettingsGetNumSubgraphInfo(
const TfLiteTelemetryInterpreterSettings* settings) {
if (settings == nullptr) return 0;
return settings->subgraph_infos.size();
}
const TfLiteTelemetrySubgraphInfo*
TfLiteTelemetryInterpreterSettingsGetSubgraphInfo(
const TfLiteTelemetryInterpreterSettings* settings) {
if (settings == nullptr) return nullptr;
return settings->subgraph_infos.data();
}
size_t TfLiteTelemetrySubgraphInfoGetNumQuantizations(
TfLiteTelemetrySubgraphInfo* subgraph_info) {
if (subgraph_info == nullptr) return 0;
return subgraph_info->quantizations.size();
}
const TfLiteQuantization* TfLiteTelemetrySubgraphInfoGetQuantizations(
TfLiteTelemetrySubgraphInfo* subgraph_info) {
if (subgraph_info == nullptr) return nullptr;
return subgraph_info->quantizations.data();
}
size_t TfLiteTelemetryGpuDelegateSettingsGetNumNodesDelegated(
const TfLiteTelemetryGpuDelegateSettings* settings) {
if (settings == nullptr) return 0;
return settings->num_nodes_delegated;
}
int TfLiteTelemetryGpuDelegateSettingsGetBackend(
const TfLiteTelemetryGpuDelegateSettings* settings) {
if (settings == nullptr) return 0;
return settings->backend;
}
} // extern "C"
@@ -0,0 +1,58 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_INTERNAL_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_INTERNAL_H_
#include <cstdint>
#include <memory>
#include <vector>
#include "tensorflow/lite/core/c/common.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
struct TfLiteTelemetryConversionMetadata {
std::vector<int32_t> model_optimization_modes;
};
struct TfLiteTelemetrySubgraphInfo {
std::vector<TfLiteQuantization> quantizations;
};
struct TfLiteTelemetryInterpreterSettings {
std::unique_ptr<TfLiteTelemetryConversionMetadata> conversion_metadata;
std::vector<TfLiteTelemetrySubgraphInfo> subgraph_infos;
};
struct TfLiteTelemetryGpuDelegateSettings {
// Reported by "GpuDelegate::DelegatePrepare" event.
size_t num_nodes_delegated;
// Reported by "GpuDelegateKernel::Prepare" event.
enum Backend : int {
UNKNOWN = 0,
OPENCL = 1,
OPENGL = 2,
};
Backend backend;
};
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_C_TELEMETRY_SETTING_INTERNAL_H_
@@ -0,0 +1,158 @@
/* Copyright 2022 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/profiling/telemetry/profiler.h"
#include <cstdint>
#include "tensorflow/lite/core/api/profiler.h"
namespace tflite::telemetry {
void TelemetryProfiler::AddEvent(const char* tag, EventType event_type,
uint64_t metric, int64_t event_metadata1,
int64_t event_metadata2) {
switch (event_type) {
case EventType::TELEMETRY_EVENT:
case EventType::TELEMETRY_DELEGATE_EVENT: {
// When the event_metadata1 is set to -1, the event is not associated
// with a particular op. See telemetry.cc.
if (event_metadata1 == -1) {
ReportTelemetryEvent(tag, TelemetryStatusCode(metric));
} else {
ReportTelemetryOpEvent(tag, event_metadata1, event_metadata2,
TelemetryStatusCode(metric));
}
break;
}
case EventType::OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_PROFILED_OPERATOR_INVOKE_EVENT: {
ReportOpInvokeEvent(tag, metric, event_metadata1, event_metadata2);
break;
}
default:
// Rejects other event types.
return;
}
}
void TelemetryProfiler::AddEventWithData(const char* tag, EventType event_type,
const void* data) {
switch (event_type) {
case EventType::TELEMETRY_REPORT_SETTINGS:
case EventType::TELEMETRY_DELEGATE_REPORT_SETTINGS: {
auto* settings = reinterpret_cast<const TfLiteTelemetrySettings*>(data);
if (settings) {
ReportSettings(tag, settings);
}
break;
}
default:
// No other AddEventWithData will be accepted for telemetry.
return;
}
}
uint32_t TelemetryProfiler::BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1,
int64_t event_metadata2) {
switch (event_type) {
case EventType::OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_OPERATOR_INVOKE_EVENT:
case EventType::DELEGATE_PROFILED_OPERATOR_INVOKE_EVENT: {
return ReportBeginOpInvokeEvent(tag, event_metadata1, event_metadata2);
}
default:
// Telemetry Profiler does not accept other event types with BeginEvent.
return UINT32_MAX;
}
}
void TelemetryProfiler::EndEvent(uint32_t event_handle) {
if (event_handle == UINT32_MAX) return;
ReportEndOpInvokeEvent(event_handle);
}
// This TfLiteTelemetryProfiler class wraps the `TfLiteTelemetryProfilerStruct`
// C API into the TelemetryProfiler interface. Users that uses the C API
// will provide `TfLiteTelemetryProfilerStruct` to TfLiteInterpreter and then
// TFLite runtime will build `TfLiteTelemetryProfiler` with it and register to
// the interpreter.
class TfLiteTelemetryProfiler : public TelemetryProfiler {
public:
explicit TfLiteTelemetryProfiler(TfLiteTelemetryProfilerStruct* profiler)
: profiler_(profiler) {}
void ReportTelemetryEvent(const char* event_name,
TelemetryStatusCode status) override;
void ReportTelemetryOpEvent(const char* event_name, int64_t op_idx,
int64_t subgraph_idx,
TelemetryStatusCode status) override;
void ReportSettings(const char* setting_name,
const TfLiteTelemetrySettings* settings) override;
uint32_t ReportBeginOpInvokeEvent(const char* op_name, int64_t op_idx,
int64_t subgraph_idx) override;
void ReportEndOpInvokeEvent(uint32_t event_handle) override;
void ReportOpInvokeEvent(const char* op_name, uint64_t elapsed_time,
int64_t op_idx, int64_t subgraph_idx) override;
private:
// Owned by TfLiteTelemetryProfiler.
// Note, profiler_->data is owned by the caller.
TfLiteTelemetryProfilerStruct* profiler_ = nullptr;
};
void TfLiteTelemetryProfiler::ReportTelemetryEvent(const char* event_name,
TelemetryStatusCode status) {
profiler_->ReportTelemetryEvent(profiler_, event_name, status.code());
}
void TfLiteTelemetryProfiler::ReportTelemetryOpEvent(
const char* event_name, int64_t op_idx, int64_t subgraph_idx,
TelemetryStatusCode status) {
profiler_->ReportTelemetryOpEvent(profiler_, event_name, op_idx, subgraph_idx,
status.code());
}
void TfLiteTelemetryProfiler::ReportSettings(
const char* setting_name, const TfLiteTelemetrySettings* settings) {
profiler_->ReportSettings(profiler_, setting_name, settings);
}
uint32_t TfLiteTelemetryProfiler::ReportBeginOpInvokeEvent(
const char* op_name, int64_t op_idx, int64_t subgraph_idx) {
return profiler_->ReportBeginOpInvokeEvent(profiler_, op_name, op_idx,
subgraph_idx);
}
void TfLiteTelemetryProfiler::ReportEndOpInvokeEvent(uint32_t event_handle) {
profiler_->ReportEndOpInvokeEvent(profiler_, event_handle);
}
void TfLiteTelemetryProfiler::ReportOpInvokeEvent(const char* op_name,
uint64_t elapsed_time,
int64_t op_idx,
int64_t subgraph_idx) {
profiler_->ReportOpInvokeEvent(profiler_, op_name, elapsed_time, op_idx,
subgraph_idx);
}
TelemetryProfiler* MakeTfLiteTelemetryProfiler(
TfLiteTelemetryProfilerStruct* profiler) {
return new TfLiteTelemetryProfiler(profiler);
}
} // namespace tflite::telemetry
@@ -0,0 +1,104 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_PROFILER_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_PROFILER_H_
#include <cstdint>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/telemetry/c/profiler.h"
#include "tensorflow/lite/profiling/telemetry/c/telemetry_setting.h"
#include "tensorflow/lite/profiling/telemetry/telemetry_status.h"
namespace tflite::telemetry {
// Telemetry profiler interface.
// When installed, the telemetry profilers accepts profiler events exported from
// TFLite runtime profiler instrumentation points, interprets the events
// based on the event type and forward to corresponding `Report` function.
// The implementation of the `Report` functions are responsible for dumping the
// profiling events to the data sink.
// The implementation of TelemetryProfiler is required to be thread safe.
class TelemetryProfiler : public Profiler {
public:
// General Telemetry events.
// Reports a telemetry event with status.
// `event_name` indicates the name of the event (e.g. "Invoke") and should not
// be nullptr.
// `status` shows 1) the source of the event, interpreter or which delegate,
// 2) the return status of the event.
virtual void ReportTelemetryEvent(const char* event_name,
TelemetryStatusCode status) = 0;
// Reports an op telemetry event with status.
// Same as `ReportTelemetryEvent`, with additional args `op_idx` and
// `subgraph_idx`.
virtual void ReportTelemetryOpEvent(const char* event_name, int64_t op_idx,
int64_t subgraph_idx,
TelemetryStatusCode status) = 0;
// Telemetry ReportSettings events.
// Reports the model and interpreter settings.
// `setting_name` indicates the name of the setting and should not be nullptr.
// `settings`'s lifespan is not guaranteed outside the scope of
// `ReportSettings` call.
virtual void ReportSettings(const char* setting_name,
const TfLiteTelemetrySettings* settings) = 0;
// Performance measurement events.
// Signals the beginning of an operator invocation.
// `op_name` is the name of the operator and should not be nullptr.
// Op invoke event are triggered with OPERATOR_INVOKE_EVENT type for TfLite
// ops and delegate kernels, and DELEGATE_OPERATOR_INVOKE_EVENT for delegate
// ops within a delegate kernels, if the instrumentation is in place.
// Returns event handle which can be passed to `EndOpInvokeEvent` later.
virtual uint32_t ReportBeginOpInvokeEvent(const char* op_name, int64_t op_idx,
int64_t subgraph_idx) = 0;
// Signals the end to the event specified by `event_handle`.
virtual void ReportEndOpInvokeEvent(uint32_t event_handle) = 0;
// For op / delegate op with built-in performance measurements, they
// are able to report the elapsed time directly.
// `elapsed_time` is in microsecond.
virtual void ReportOpInvokeEvent(const char* op_name, uint64_t elapsed_time,
int64_t op_idx, int64_t subgraph_idx) = 0;
private:
// Methods inherited from TfLite Profiler.
// TelemetryProfiler will dispatch the event signals to appropriate `Report`
// functinos defined above based on the event type.
// Subclasses should not override those following methods.
void AddEvent(const char* tag, EventType event_type, uint64_t metric,
int64_t event_metadata1, int64_t event_metadata2) final;
void AddEventWithData(const char* tag, EventType event_type,
const void* data) final;
uint32_t BeginEvent(const char* tag, EventType event_type,
int64_t event_metadata1, int64_t event_metadata2) final;
void EndEvent(uint32_t event_handle) final;
};
// Creates a concrete TelemetryProfiler that wraps the
// `TfLiteTelemetryProfilerStruct` C API.
TelemetryProfiler* MakeTfLiteTelemetryProfiler(
TfLiteTelemetryProfilerStruct* profiler);
} // namespace tflite::telemetry
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_PROFILER_H_
@@ -0,0 +1,154 @@
/* Copyright 2022 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/profiling/telemetry/profiler.h"
#include <cstdint>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/profiling/telemetry/c/telemetry_setting.h"
#include "tensorflow/lite/profiling/telemetry/telemetry_status.h"
namespace tflite::telemetry {
namespace {
constexpr char kEventName[] = "event_name";
constexpr char kSettingName[] = "setting_name";
class MockTelemtryProfiler : public TelemetryProfiler {
public:
MOCK_METHOD(void, ReportTelemetryEvent,
(const char* event_name, TelemetryStatusCode status), (override));
MOCK_METHOD(void, ReportTelemetryOpEvent,
(const char* event_name, int64_t op_idx, int64_t subgraph_idx,
TelemetryStatusCode status),
(override));
MOCK_METHOD(void, ReportSettings,
(const char* setting_name,
const TfLiteTelemetrySettings* settings),
(override));
MOCK_METHOD(uint32_t, ReportBeginOpInvokeEvent,
(const char* op_name, int64_t op_idx, int64_t subgraph_idx),
(override));
MOCK_METHOD(void, ReportEndOpInvokeEvent, (uint32_t event_handle),
(override));
MOCK_METHOD(void, ReportOpInvokeEvent,
(const char* op_name, uint64_t elapsed_time, int64_t op_idx,
int64_t subgraph_idx),
(override));
};
class TelemetryStructTest : public ::testing::Test {
protected:
TelemetryStructTest() {
context_.profiler = &profiler_;
profiler_struct_.data = &mock_profiler_;
profiler_struct_.ReportTelemetryEvent =
[](struct TfLiteTelemetryProfilerStruct* profiler,
const char* event_name, uint64_t status) {
static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportTelemetryEvent(
event_name, tflite::telemetry::TelemetryStatusCode(status));
};
profiler_struct_.ReportTelemetryOpEvent =
[](struct TfLiteTelemetryProfilerStruct* profiler,
const char* event_name, int64_t op_idx, int64_t subgraph_idx,
uint64_t status) {
static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportTelemetryOpEvent(
event_name, op_idx, subgraph_idx,
tflite::telemetry::TelemetryStatusCode(status));
};
profiler_struct_.ReportSettings =
[](struct TfLiteTelemetryProfilerStruct* profiler,
const char* setting_name, const TfLiteTelemetrySettings* settings) {
static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportSettings(setting_name, settings);
};
profiler_struct_.ReportBeginOpInvokeEvent =
[](struct TfLiteTelemetryProfilerStruct* profiler, const char* op_name,
int64_t op_idx, int64_t subgraph_idx) -> uint32_t {
return static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportBeginOpInvokeEvent(op_name, op_idx, subgraph_idx);
};
profiler_struct_.ReportEndOpInvokeEvent =
[](struct TfLiteTelemetryProfilerStruct* profiler,
uint32_t event_handle) {
return static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportEndOpInvokeEvent(event_handle);
};
profiler_struct_.ReportOpInvokeEvent =
[](struct TfLiteTelemetryProfilerStruct* profiler, const char* op_name,
uint64_t elapsed_time, int64_t op_idx, int64_t subgraph_idx) {
return static_cast<MockTelemtryProfiler*>(profiler->data)
->ReportOpInvokeEvent(op_name, elapsed_time, op_idx,
subgraph_idx);
};
profiler_.reset(telemetry::MakeTfLiteTelemetryProfiler(&profiler_struct_));
}
MockTelemtryProfiler mock_profiler_;
std::unique_ptr<TelemetryProfiler> profiler_;
TfLiteContext context_;
TfLiteTelemetryProfilerStruct profiler_struct_;
};
TEST_F(TelemetryStructTest, TelemetryReportEvent) {
EXPECT_CALL(mock_profiler_,
ReportTelemetryEvent(kEventName, TelemetryStatusCode(kTfLiteOk)));
profiler_->ReportTelemetryEvent(kEventName, TelemetryStatusCode(kTfLiteOk));
}
TEST_F(TelemetryStructTest, TelemetryReportOpEvent) {
EXPECT_CALL(
mock_profiler_,
ReportTelemetryOpEvent(kEventName, 1, 2, TelemetryStatusCode(kTfLiteOk)));
profiler_->ReportTelemetryOpEvent(kEventName, 1, 2,
TelemetryStatusCode(kTfLiteOk));
}
TEST_F(TelemetryStructTest, TelemetryReportSettings) {
EXPECT_CALL(mock_profiler_, ReportSettings(kSettingName, testing::_));
TfLiteTelemetrySettings settings{};
profiler_->ReportSettings(kSettingName, &settings);
}
TEST_F(TelemetryStructTest, TelemetryReportBeginOpInvokeEvent) {
EXPECT_CALL(mock_profiler_, ReportBeginOpInvokeEvent(kSettingName, 1, 2));
profiler_->ReportBeginOpInvokeEvent(kSettingName, 1, 2);
}
TEST_F(TelemetryStructTest, TelemetryReportEndOpInvokeEvent) {
EXPECT_CALL(mock_profiler_, ReportEndOpInvokeEvent(1));
profiler_->ReportEndOpInvokeEvent(1);
}
TEST_F(TelemetryStructTest, TelemetryReportOpInvokeEvent) {
EXPECT_CALL(mock_profiler_, ReportOpInvokeEvent(kSettingName, 1, 2, 3));
profiler_->ReportOpInvokeEvent(kSettingName, 1, 2, 3);
}
} // namespace
} // namespace tflite::telemetry
@@ -0,0 +1,98 @@
/* Copyright 2022 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/profiling/telemetry/telemetry.h"
#include <cstdint>
#include "tensorflow/lite/core/api/profiler.h"
#include "tensorflow/lite/profiling/telemetry/telemetry_status.h"
namespace tflite::telemetry {
void TelemetryReportEvent(TfLiteContext* context, const char* event_name,
TfLiteStatus status) {
if (context->profiler) {
reinterpret_cast<Profiler*>(context->profiler)
->AddEvent(event_name, Profiler::EventType::TELEMETRY_EVENT,
TelemetryStatusCode(status).code(),
/*event_metadata=*/-1);
}
}
void TelemetryReportOpEvent(TfLiteContext* context, const char* op_name,
int64_t op_index, int64_t subgraph_index,
TfLiteStatus status) {
if (context->profiler) {
reinterpret_cast<Profiler*>(context->profiler)
->AddEvent(op_name, Profiler::EventType::TELEMETRY_EVENT,
TelemetryStatusCode(status).code(), op_index,
subgraph_index);
}
}
void TelemetryReportDelegateEvent(TfLiteContext* context,
const char* event_name,
TelemetrySource source, uint32_t code) {
if (context->profiler) {
reinterpret_cast<Profiler*>(context->profiler)
->AddEvent(event_name, Profiler::EventType::TELEMETRY_DELEGATE_EVENT,
TelemetryStatusCode(source, code).code(),
/*event_metadata=*/-1);
}
}
void TelemetryReportDelegateOpEvent(TfLiteContext* context, const char* op_name,
int64_t op_index, int64_t subgraph_index,
TelemetrySource source, uint32_t code) {
if (context->profiler) {
reinterpret_cast<Profiler*>(context->profiler)
->AddEvent(op_name, Profiler::EventType::TELEMETRY_DELEGATE_EVENT,
TelemetryStatusCode(source, code).code(), op_index,
subgraph_index);
}
}
void TelemetryReportSettings(
TfLiteContext* context, const char* setting_name,
const TfLiteTelemetryInterpreterSettings* settings) {
auto* profiler = reinterpret_cast<Profiler*>(context->profiler);
if (profiler) {
TfLiteTelemetrySettings telemetry_settings{};
telemetry_settings.source =
static_cast<uint32_t>(TelemetrySource::TFLITE_INTERPRETER);
telemetry_settings.data = reinterpret_cast<const void*>(settings);
profiler->AddEventWithData(
setting_name, Profiler::EventType::TELEMETRY_REPORT_SETTINGS,
reinterpret_cast<const void*>(&telemetry_settings));
}
}
void TelemetryReportDelegateSettings(TfLiteContext* context,
const char* setting_name,
TelemetrySource source,
const void* settings) {
auto* profiler = reinterpret_cast<Profiler*>(context->profiler);
if (profiler) {
TfLiteTelemetrySettings telemetry_settings{};
telemetry_settings.source = static_cast<uint32_t>(source);
telemetry_settings.data = settings;
profiler->AddEventWithData(
setting_name, Profiler::EventType::TELEMETRY_DELEGATE_REPORT_SETTINGS,
reinterpret_cast<const void*>(&telemetry_settings));
}
}
} // namespace tflite::telemetry
@@ -0,0 +1,75 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_TELEMETRY_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_TELEMETRY_H_
#include <cstdint>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/profiling/telemetry/c/telemetry_setting.h"
#include "tensorflow/lite/profiling/telemetry/telemetry_status.h"
namespace tflite::telemetry {
// Methods for instrumenting TFLite runtime to export telemetry events to
// profilers.
// Reports an interpreter telemetry event.
// `event_name` indicates the name of the event (e.g. "Invoke") and should not
// be nullptr.
void TelemetryReportEvent(TfLiteContext* context, const char* event_name,
TfLiteStatus status);
// Reports an interpreter telemetry event associated with an op.
// `op_name` indicates the name of the op and should not be nullptr.
void TelemetryReportOpEvent(TfLiteContext* context, const char* op_name,
int64_t op_index, int64_t subgraph_index,
TfLiteStatus status);
// Reports a delegate telemetry event.
// `event_name` indicates the name of the event (e.g. "Invoke") and should not
// be nullptr.
// `source` indicates which delegate the event is from.
// `code` is the error code from the delegate.
void TelemetryReportDelegateEvent(TfLiteContext* context,
const char* event_name,
TelemetrySource source, uint32_t code);
// Reports a delegate telemetry event associated with an op.
// `op_name` indicates the name of the op and should not be nullptr.
void TelemetryReportDelegateOpEvent(TfLiteContext* context, const char* op_name,
int64_t op_index, int64_t subgraph_index,
TelemetrySource source, uint32_t code);
// Reports model and interpreter level settings.
// `setting_name` indicates the name of the setting.
void TelemetryReportSettings(
TfLiteContext* context, const char* setting_name,
const TfLiteTelemetryInterpreterSettings* settings);
// Reports delegate settings.
// `setting_name` indicates the name of the setting.
// `source` indicates which delegate the event is from.
// `settings` is the delegate provided settings and should not be nullptr.
void TelemetryReportDelegateSettings(TfLiteContext* context,
const char* setting_name,
TelemetrySource source,
const void* settings);
} // namespace tflite::telemetry
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_TELEMETRY_H_
@@ -0,0 +1,70 @@
/* Copyright 2022 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_PROFILING_TELEMETRY_TELEMETRY_STATUS_H_
#define TENSORFLOW_LITE_PROFILING_TELEMETRY_TELEMETRY_STATUS_H_
#include <cstdint>
#include "tensorflow/lite/core/c/c_api_types.h"
namespace tflite::telemetry {
// The source of a telemetry event. Enum values intentionally follow proto
// guidelines as they are used for Clearcut logging.
enum class TelemetrySource : uint32_t {
UNKNOWN = 0,
TFLITE_INTERPRETER = 1,
// For external delegate.
// External delegate should identify themselves in telemetry event names by
// prefixing the delegame name to it.
TFLITE_CUSTOM_DELEGATE = 2,
TFLITE_GPU = 3,
TFLITE_NNAPI = 4,
TFLITE_HEXAGON = 5,
TFLITE_XNNPACK = 6,
TFLITE_COREML = 7,
};
// A namespaced status code for telemetry events.
struct TelemetryStatusCode {
TelemetrySource source = TelemetrySource::TFLITE_INTERPRETER;
uint32_t status_code = 0;
// Helper constructors to build the status code from various types.
TelemetryStatusCode() = default;
TelemetryStatusCode(TelemetrySource source, uint32_t status_code)
: source(source), status_code(status_code) {}
explicit TelemetryStatusCode(TfLiteStatus status)
: TelemetryStatusCode(TelemetrySource::TFLITE_INTERPRETER, status) {}
explicit TelemetryStatusCode(uint64_t code)
: TelemetryStatusCode(static_cast<TelemetrySource>(code >> 32),
static_cast<uint32_t>(code)) {}
// Returns the uint64_t representation of the status code.
uint64_t code() const {
return (static_cast<uint64_t>(source) << 32 | status_code);
}
bool operator==(const TelemetryStatusCode& other) const {
return source == other.source && status_code == other.status_code;
}
};
} // namespace tflite::telemetry
#endif // TENSORFLOW_LITE_PROFILING_TELEMETRY_TELEMETRY_STATUS_H_
@@ -0,0 +1,114 @@
/* Copyright 2022 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/profiling/telemetry/telemetry.h"
#include <cstdint>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/profiling/telemetry/c/telemetry_setting_internal.h"
#include "tensorflow/lite/profiling/telemetry/profiler.h"
#include "tensorflow/lite/profiling/telemetry/telemetry_status.h"
namespace tflite::telemetry {
namespace {
constexpr char kEventName[] = "event_name";
constexpr char kSettingName[] = "setting_name";
class MockTelemetryProfiler : public TelemetryProfiler {
public:
MOCK_METHOD(void, ReportTelemetryEvent,
(const char* event_name, TelemetryStatusCode status), (override));
MOCK_METHOD(void, ReportTelemetryOpEvent,
(const char* event_name, int64_t op_idx, int64_t subgraph_idx,
TelemetryStatusCode status),
(override));
MOCK_METHOD(void, ReportSettings,
(const char* setting_name,
const TfLiteTelemetrySettings* settings),
(override));
MOCK_METHOD(uint32_t, ReportBeginOpInvokeEvent,
(const char* op_name, int64_t op_idx, int64_t subgraph_idx),
(override));
MOCK_METHOD(void, ReportEndOpInvokeEvent, (uint32_t event_handle),
(override));
MOCK_METHOD(void, ReportOpInvokeEvent,
(const char* op_name, uint64_t elapsed_time, int64_t op_idx,
int64_t subgraph_idx),
(override));
};
class TelemetryTest : public ::testing::Test {
protected:
TelemetryTest() { context_.profiler = &profiler_; }
MockTelemetryProfiler profiler_;
TfLiteContext context_;
};
TEST_F(TelemetryTest, TelemetryReportEvent) {
EXPECT_CALL(profiler_,
ReportTelemetryEvent(kEventName, TelemetryStatusCode(kTfLiteOk)));
TelemetryReportEvent(&context_, kEventName, kTfLiteOk);
}
TEST_F(TelemetryTest, TelemetryReportOpEvent) {
EXPECT_CALL(profiler_, ReportTelemetryOpEvent(
kEventName, 1, 2, TelemetryStatusCode(kTfLiteOk)));
TelemetryReportOpEvent(&context_, kEventName, 1, 2, kTfLiteOk);
}
TEST_F(TelemetryTest, TelemetryReportDelegateEvent) {
EXPECT_CALL(profiler_, ReportTelemetryEvent(
kEventName, TelemetryStatusCode(
TelemetrySource::TFLITE_GPU, 21)));
TelemetryReportDelegateEvent(&context_, kEventName,
TelemetrySource::TFLITE_GPU, 21);
}
TEST_F(TelemetryTest, TelemetryReportDelegateOpEvent) {
EXPECT_CALL(profiler_,
ReportTelemetryOpEvent(
kEventName, 1, 2,
TelemetryStatusCode(TelemetrySource::TFLITE_GPU, 21)));
TelemetryReportDelegateOpEvent(&context_, kEventName, 1, 2,
TelemetrySource::TFLITE_GPU, 21);
}
TEST_F(TelemetryTest, TelemetryReportSettings) {
EXPECT_CALL(profiler_, ReportSettings(kSettingName, testing::_));
TfLiteTelemetryInterpreterSettings settings{};
TelemetryReportSettings(&context_, kSettingName, &settings);
}
TEST_F(TelemetryTest, TelemetryReportDelegateSettings) {
std::string settings = "gpu delegate settings";
EXPECT_CALL(profiler_, ReportSettings(kSettingName, testing::_));
TelemetryReportDelegateSettings(&context_, kSettingName,
TelemetrySource::TFLITE_GPU, &settings);
}
} // namespace
} // namespace tflite::telemetry
+69
View File
@@ -0,0 +1,69 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/time.h"
#if defined(_MSC_VER)
#include <chrono> // NOLINT(build/c++11)
#include <thread> // NOLINT(build/c++11)
#else
#include <sys/time.h>
#include <time.h>
#endif
namespace tflite {
namespace profiling {
namespace time {
#if defined(_MSC_VER)
uint64_t NowMicros() {
return static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count());
}
void SleepForMicros(uint64_t micros) {
std::this_thread::sleep_for(std::chrono::microseconds(micros));
}
#else
uint64_t NowMicros() {
#if defined(__APPLE__)
// Prefer using CLOCK_MONOTONIC_RAW for measuring duration and latency on
// macOS.
return clock_gettime_nsec_np(CLOCK_MONOTONIC_RAW) / 1e3;
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<uint64_t>(ts.tv_sec) * 1e6 +
static_cast<uint64_t>(ts.tv_nsec) / 1e3;
#endif // __APPLE__
}
void SleepForMicros(uint64_t micros) {
timespec sleep_time;
sleep_time.tv_sec = micros / 1e6;
micros -= sleep_time.tv_sec * 1e6;
sleep_time.tv_nsec = micros * 1e3;
nanosleep(&sleep_time, nullptr);
}
#endif // defined(_MSC_VER)
} // namespace time
} // namespace profiling
} // namespace tflite
+28
View File
@@ -0,0 +1,28 @@
/* 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.
==============================================================================*/
#ifndef TENSORFLOW_LITE_PROFILING_TIME_H_
#define TENSORFLOW_LITE_PROFILING_TIME_H_
#include <cstdint>
namespace tflite {
namespace profiling {
namespace time {
uint64_t NowMicros();
void SleepForMicros(uint64_t micros);
} // namespace time
} // namespace profiling
} // namespace tflite
#endif // TENSORFLOW_LITE_PROFILING_TIME_H_
+49
View File
@@ -0,0 +1,49 @@
/* 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.
==============================================================================*/
#include "tensorflow/lite/profiling/time.h"
#include <gtest/gtest.h>
namespace tflite {
namespace profiling {
namespace time {
TEST(TimeTest, NowMicros) {
auto now0 = NowMicros();
EXPECT_GT(now0, 0);
auto now1 = NowMicros();
EXPECT_GE(now1, now0);
}
TEST(TimeTest, SleepForMicros) {
// A zero sleep shouldn't cause issues.
SleepForMicros(0);
// Sleeping should be reflected in the current time.
auto now0 = NowMicros();
SleepForMicros(50);
auto now1 = NowMicros();
EXPECT_GE(now1, now0 + 50);
// Sleeping more than a second should function properly.
now0 = NowMicros();
SleepForMicros(1e6 + 50);
now1 = NowMicros();
EXPECT_GE(now1, now0 + 1e6 + 50);
}
} // namespace time
} // namespace profiling
} // namespace tflite