blob: 72614083c748f02914a7076555bef3bb7a663209 [file]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <executorch/backends/xnnpack/runtime/profiling/XNNProfiler.h>
#include <executorch/runtime/core/error.h>
#include <executorch/runtime/core/event_tracer.h>
// Headers used only when event tracer is compiled in.
// NOLINTBEGIN
#include <executorch/runtime/platform/log.h>
#include <executorch/runtime/platform/platform.h>
#include <executorch/runtime/platform/types.h>
#include <cinttypes>
#include <cstring>
#include <string>
#include <unordered_map>
// NOLINTEND
namespace executorch::backends::xnnpack::delegate::profiling {
using executorch::runtime::Error;
using executorch::runtime::EventTracer;
#if defined(ET_EVENT_TRACER_ENABLED) || defined(ENABLE_XNNPACK_PROFILING)
XNNProfiler::XNNProfiler()
: state_(XNNProfilerState::Uninitialized), run_count_(0) {}
Error XNNProfiler::initialize(xnn_runtime_t runtime) {
runtime_ = runtime;
// Fetch the runtime operator information from XNNPACK.
ET_CHECK_OK_OR_RETURN_ERROR(get_runtime_num_operators());
ET_CHECK_OK_OR_RETURN_ERROR(get_runtime_operator_names());
state_ = XNNProfilerState::Ready;
return Error::Ok;
}
Error XNNProfiler::start(EventTracer* event_tracer) {
// Validate profiler state.
if (state_ == XNNProfilerState::Uninitialized) {
ET_LOG(
Error,
"XNNProfiler must be initialized prior to calling begin_execution.");
return Error::InvalidState;
} else if (state_ == XNNProfilerState::Running) {
ET_LOG(
Error,
"XNNProfiler is already running. Call end_execution() before calling begin_execution().");
return Error::InvalidState;
}
event_tracer_ = event_tracer;
state_ = XNNProfilerState::Running;
// Log the start of execution timestamp.
start_time_ = et_pal_current_ticks();
return Error::Ok;
}
Error XNNProfiler::end() {
// Validate profiler state.
ET_CHECK_OR_RETURN_ERROR(
state_ == XNNProfilerState::Running,
InvalidState,
"XNNProfiler is not running. Ensure begin_execution() is called before end_execution().");
// Retrieve operator timing from XNNPACK.
ET_CHECK_OK_OR_RETURN_ERROR(get_runtime_operator_timings());
if (event_tracer_ != nullptr) {
submit_trace();
}
log_operator_timings();
state_ = XNNProfilerState::Ready;
return Error::Ok;
}
Error XNNProfiler::get_runtime_operator_names() {
size_t required_size = 0;
// First call returns xnn_status_out_of_memory, but sets required_size to
// the correct size of the buffer to store the result.
xnn_status status = xnn_get_runtime_profiling_info(
runtime_, // runtime
xnn_profile_info_operator_name, // param_name
0, // param_value_size
nullptr, // param_value
&required_size // param_value_size_ret
);
if (status == xnn_status_out_of_memory) {
op_names_.resize(required_size);
status = xnn_get_runtime_profiling_info(
runtime_,
xnn_profile_info_operator_name,
op_names_.size(),
op_names_.data(),
&required_size);
}
if (status != xnn_status_success) {
ET_LOG(Error, "Failed to get XNNPACK operator names: %d", status);
return Error::Internal;
}
return Error::Ok;
}
Error XNNProfiler::get_runtime_num_operators() {
size_t required_size = 0;
xnn_status status = xnn_get_runtime_profiling_info(
runtime_,
xnn_profile_info_num_operators,
sizeof(op_count_),
&op_count_,
&required_size);
if (status != xnn_status_success) {
ET_LOG(Error, "Failed to get XNNPACK operator count: %d", status);
return Error::Internal;
}
return Error::Ok;
}
Error XNNProfiler::get_runtime_operator_timings() {
size_t required_size;
// Get number of runtime operators for timing_stats.size
op_timings_.resize(op_count_);
xnn_status status = xnn_get_runtime_profiling_info(
runtime_,
xnn_profile_info_operator_timing,
op_timings_.size() * sizeof(uint64_t),
op_timings_.data(),
&required_size);
if (status != xnn_status_success) {
ET_LOG(Error, "Failed to get XNNPACK operator timing: %d", status);
return Error::Internal;
}
return Error::Ok;
}
void XNNProfiler::log_operator_timings() {
#ifdef ENABLE_XNNPACK_PROFILING
// Update running average state and log average timing for each op.
run_count_++;
size_t name_len = 0;
const char* op_name = nullptr;
auto total_time = 0.0f;
if (op_timings_sum_.size() != op_count_) {
op_timings_sum_ = std::vector<uint64_t>(op_count_, 0);
}
for (size_t i = 0; i < op_count_; i++) {
op_name = &op_names_[name_len];
name_len += strlen(op_name) + 1;
op_timings_sum_[i] += op_timings_[i];
auto avg_op_time = op_timings_sum_[i] / static_cast<float>(run_count_);
total_time += avg_op_time;
ET_LOG(
Info, ">>, %s, %" PRId64 " (%f)", op_name, op_timings_[i], avg_op_time);
}
ET_LOG(Info, ">>, Total Time, %f", total_time);
#else
run_count_++;
#endif
}
void XNNProfiler::submit_trace() {
// Retrieve the system tick rate (ratio between ticks and nanoseconds).
auto tick_ns_conv_multiplier = et_pal_ticks_to_ns_multiplier();
ET_CHECK(op_timings_.size() == op_count_);
size_t name_len = 0;
et_timestamp_t time = start_time_;
std::unordered_map<std::string, uint32_t> op_counts;
for (auto i = 0u; i < op_count_; i++) {
auto op_name = &op_names_[name_len];
name_len += strlen(op_name) + 1;
// Format the op name as {name} #{count}.
auto op_name_str = std::string(op_name);
op_counts[op_name_str]++;
auto name_formatted =
op_name_str + " #" + std::to_string(op_counts[op_name_str]);
// Convert from microseconds (XNNPACK) to PAL ticks (ET).
// The tick_ns_conv_ratio is ns / tick. We want ticks:
// ticks = us * (ns / us) / conv_ratio
// = us * 1000 * conv_ratio.denom / conv_ratio.num
auto interval_ticks = static_cast<et_timestamp_t>(
op_timings_[i] * 1000 * tick_ns_conv_multiplier.denominator /
tick_ns_conv_multiplier.numerator);
auto end_time = time + interval_ticks;
executorch::runtime::event_tracer_log_profiling_delegate(
event_tracer_,
name_formatted.c_str(),
/*delegate_debug_id=*/static_cast<executorch::runtime::DebugHandle>(-1),
time,
end_time);
// Assume that the next op starts immediately after the previous op.
// This may not be strictly true, but it should be close enough.
// Ideally, we'll get the start and end times from XNNPACK in the
// future.
time = end_time;
}
}
#else // defined(ET_EVENT_TRACER_ENABLED) || defined(ENABLE_XNNPACK_PROFILING)
// Stub implementation for when profiling is disabled.
XNNProfiler::XNNProfiler() {}
Error XNNProfiler::initialize(xnn_runtime_t runtime) {
(void)runtime;
return Error::Ok;
}
Error XNNProfiler::start(EventTracer* event_tracer) {
(void)event_tracer;
return Error::Ok;
}
Error XNNProfiler::end() {
return Error::Ok;
}
#endif
} // namespace executorch::backends::xnnpack::delegate::profiling