Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions backends/arm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ if(EXECUTORCH_BUILD_ARM_BAREMETAL OR EXECUTORCH_BUILD_ARM_ETHOSU_LINUX)
target_include_directories(
ethosu_execution_stats PRIVATE ${_common_include_directories}
)
target_include_directories(
ethosu_execution_stats PRIVATE ${DRIVER_ETHOSU_INCLUDE_DIR}
)
target_link_libraries(ethosu_execution_stats PUBLIC ethosu_core_driver)
elseif(EXECUTORCH_BUILD_ARM_ETHOSU_LINUX)
target_sources(
executorch_delegate_ethos_u
Expand Down
5 changes: 5 additions & 0 deletions backends/arm/runtime/EthosUBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <vector>

#include <executorch/backends/arm/runtime/EthosUBackend_Internal.h>
#include <executorch/backends/arm/runtime/EthosUBackend_Pmu.h>
#include <executorch/backends/arm/runtime/VelaBinStream.h>
#include <executorch/runtime/backend/interface.h>
#include <executorch/runtime/core/error.h>
Expand Down Expand Up @@ -56,6 +57,10 @@ namespace executorch {
namespace backends {
namespace arm {

bool __attribute__((weak)) ethosu_execution_metrics_enabled() noexcept {
return false;
}

namespace {

using printf_size_t = unsigned long;
Expand Down
6 changes: 5 additions & 1 deletion backends/arm/runtime/EthosUBackend_CoreDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <ethosu_driver.h>

#include <executorch/backends/arm/runtime/EthosUBackend_Internal.h>
#include <executorch/backends/arm/runtime/EthosUBackend_Pmu.h>
#include <executorch/runtime/core/error.h>

using executorch::runtime::BackendExecutionContext;
Expand Down Expand Up @@ -133,14 +134,17 @@ Error platform_execute(
handles.scratch_data_size,
ethosu_fast_scratch_size};
#endif
executorch::backends::arm::EthosUInvocationContext invocation_context{};
invocation_context.delegate_handle =
reinterpret_cast<uintptr_t>(execution_handle);
int result = ethosu_invoke_v3(
driver.get(),
static_cast<const void*>(handles.cmd_data),
handles.cmd_data_size,
bases,
bases_size,
ETHOSU_NUM_BASE_ADDRS, /* fixed array of pointers to binary interface*/
nullptr);
&invocation_context);

if (result != 0) {
ET_LOG(Error, "Ethos-U invocation failed error (%d)", result);
Expand Down
24 changes: 24 additions & 0 deletions backends/arm/runtime/EthosUBackend_Pmu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2026 Arm Limited and/or its affiliates.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once

#include <cstdint>

namespace executorch::backends::arm {

// EthosUStatsBuffer is defined in EthosUExecutionStats.h.
struct EthosUStatsBuffer;

struct EthosUInvocationContext {
EthosUStatsBuffer* buffer = nullptr;
uintptr_t delegate_handle = 0;
uint32_t counter_count = 0;
};

bool ethosu_execution_metrics_enabled() noexcept;

} // namespace executorch::backends::arm
121 changes: 121 additions & 0 deletions backends/arm/runtime/EthosUExecutionStats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,125 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <algorithm>

#include <ethosu_driver.h>
#include <pmu_ethosu.h>

#include <executorch/backends/arm/runtime/EthosUBackend_Pmu.h>
#include <executorch/backends/arm/runtime/EthosUExecutionStats.h>

namespace executorch::backends::arm {
namespace {
std::atomic<EthosUStatsBuffer*> capture_buffer{nullptr};
} // namespace

EthosUStatsBuffer* set_ethosu_stats_buffer(EthosUStatsBuffer* buffer) noexcept {
return capture_buffer.exchange(buffer, std::memory_order_acq_rel);
}

bool ethosu_execution_metrics_enabled() noexcept {
return true;
}

} // namespace executorch::backends::arm

using executorch::backends::arm::EthosUInvocationContext;

extern "C" {
void __attribute__((weak)) EthosUBackend_npu_begin() {}

void __attribute__((weak)) EthosUBackend_npu_end() {}

void ethosu_inference_begin(struct ethosu_driver* drv, void* user_arg) {
auto* invocation = static_cast<EthosUInvocationContext*>(user_arg);
if (invocation == nullptr) {
return;
}

invocation->counter_count = 0;
invocation->buffer =
executorch::backends::arm::capture_buffer.load(std::memory_order_acquire);
if (invocation->buffer != nullptr) {
static constexpr ethosu_pmu_event_type kU55U65Events[] = {
ETHOSU_PMU_AXI0_RD_DATA_BEAT_RECEIVED,
ETHOSU_PMU_AXI1_RD_DATA_BEAT_RECEIVED,
ETHOSU_PMU_AXI0_WR_DATA_BEAT_WRITTEN,
ETHOSU_PMU_NPU_IDLE,
};
static constexpr ethosu_pmu_event_type kU85Events[] = {
ETHOSU_PMU_SRAM_RD_DATA_BEAT_RECEIVED,
ETHOSU_PMU_SRAM_WR_DATA_BEAT_WRITTEN,
ETHOSU_PMU_EXT_RD_DATA_BEAT_RECEIVED,
ETHOSU_PMU_EXT_WR_DATA_BEAT_WRITTEN,
ETHOSU_PMU_NPU_IDLE,
ETHOSU_PMU_MAC_ACTIVE,
ETHOSU_PMU_WD_ACTIVE,
};

const ethosu_pmu_event_type* events = nullptr;
uint32_t event_count = 0;
if (drv->dev.caps.product == ETHOSU_PRODUCT_U55 ||
drv->dev.caps.product == ETHOSU_PRODUCT_U65) {
events = kU55U65Events;
event_count = sizeof(kU55U65Events) / sizeof(kU55U65Events[0]);
} else if (drv->dev.caps.product == ETHOSU_PRODUCT_U85) {
events = kU85Events;
event_count = sizeof(kU85Events) / sizeof(kU85Events[0]);
}

if (events != nullptr) {
invocation->counter_count =
std::min(event_count, ETHOSU_PMU_Get_NumEventCountersForDrv(drv));
}
if (events != nullptr && invocation->counter_count != 0) {
ETHOSU_PMU_Enable(drv);
ETHOSU_PMU_PMCCNTR_CFG_Set_Stop_Event(drv, ETHOSU_PMU_NPU_IDLE);
ETHOSU_PMU_PMCCNTR_CFG_Set_Start_Event(drv, ETHOSU_PMU_NPU_ACTIVE);
for (uint32_t slot = 0; slot < invocation->counter_count; ++slot) {
ETHOSU_PMU_Set_EVTYPER(drv, slot, events[slot]);
}
const uint32_t event_mask = (1u << invocation->counter_count) - 1u;
ETHOSU_PMU_CNTR_Enable(drv, event_mask | ETHOSU_PMU_CCNT_Msk);
ETHOSU_PMU_CYCCNT_Reset(drv);
ETHOSU_PMU_EVCNTR_ALL_Reset(drv);
ETHOSU_PMU_Set_CNTR_OVS(drv, event_mask | ETHOSU_PMU_CCNT_Msk);
}
}

// Call callback
EthosUBackend_npu_begin();
}

void ethosu_inference_end(struct ethosu_driver* drv, void* user_arg) {
const auto* invocation = static_cast<EthosUInvocationContext*>(user_arg);
if (invocation == nullptr) {
return;
}

if (invocation->buffer != nullptr && invocation->counter_count != 0) {
executorch::backends::arm::EthosUExecutionRecord record{};
record.delegate_handle = invocation->delegate_handle;
record.success = drv->job.result == ETHOSU_JOB_RESULT_OK;
record.stats.device_identifier = reinterpret_cast<uintptr_t>(drv->dev.reg);
record.stats.product = drv->dev.caps.product;
record.stats.cycle_count = ETHOSU_PMU_Get_CCNTR(drv);
record.stats.cycle_count_valid = true;
const uint32_t overflow_status = ETHOSU_PMU_Get_CNTR_OVS(drv);
record.stats.cycle_overflow = (overflow_status & ETHOSU_PMU_CCNT_Msk) != 0;
record.stats.counter_count = invocation->counter_count;
for (uint32_t slot = 0; slot < invocation->counter_count; ++slot) {
record.stats.counters[slot] = {
static_cast<uint32_t>(ETHOSU_PMU_Get_EVTYPER(drv, slot)),
ETHOSU_PMU_Get_EVCNTR(drv, slot),
(overflow_status & (1u << slot)) != 0};
}
ETHOSU_PMU_Disable(drv);
EthosUBackend_npu_end();
invocation->buffer->append(record);
return;
}
EthosUBackend_npu_end();
}
}
13 changes: 13 additions & 0 deletions backends/arm/runtime/EthosUExecutionStats.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
#include <cstddef>
#include <cstdint>

// Optional runner hooks. The stats library provides weak no-op definitions.
extern "C" {
void EthosUBackend_npu_begin();
void EthosUBackend_npu_end();
}

namespace executorch::backends::arm {

// Containers of Ethos-U metrics.
Expand All @@ -21,6 +27,13 @@ struct EthosUExecutionStats;
struct EthosUExecutionRecord;
struct EthosUStatsBuffer;

// Public API of the ethosu_execution_stats library.
// Register runner-owned storage for subsequent invocations. Pass nullptr to
// stop capture; replace the buffer only when no invocations are running.
EthosUStatsBuffer* set_ethosu_stats_buffer(EthosUStatsBuffer* buffer) noexcept;
// Returns true when this library is linked.
bool ethosu_execution_metrics_enabled() noexcept;

constexpr size_t kMaxPmuEventCounters = 8;

enum class EthosUPlatform : uint8_t {
Expand Down
14 changes: 14 additions & 0 deletions backends/arm/runtime/targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ def _vgf_zero_copy_flags():
return ["-DEXECUTORCH_VGF_ZERO_COPY_IO=0"]

def define_common_targets():
runtime.cxx_library(
name = "ethosu_invocation_context",
exported_headers = ["EthosUBackend_Pmu.h"],
visibility = ["PUBLIC"],
)
runtime.cxx_library(
name = "ethosu_execution_stats",
srcs = ["EthosUExecutionStats.cpp"],
exported_headers = ["EthosUExecutionStats.h"],
exported_deps = [":ethosu_invocation_context"],
visibility = ["PUBLIC"],
deps = ["fbsource//third-party/ethos-u-core-driver:core_driver_headers_only"],
)
runtime.cxx_library(
name = "vela_bin_stream",
srcs = ["VelaBinStream.cpp"],
Expand Down Expand Up @@ -54,6 +67,7 @@ def define_common_targets():
visibility = ["PUBLIC"],
deps = [
"//executorch/runtime/backend:interface",
":ethosu_invocation_context",
":vela_bin_stream",
"//executorch/runtime/core:core",
"fbsource//third-party/ethos-u-core-driver:core_driver_headers_only",
Expand Down
43 changes: 40 additions & 3 deletions backends/arm/runtime/tests/ethos-u/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,57 @@ set_tests_properties(
)

find_package(Threads REQUIRED)
add_library(
ethosu_execution_stats_test_library STATIC
"${EXECUTORCH_ROOT}/backends/arm/runtime/EthosUExecutionStats.cpp"
)
target_include_directories(
ethosu_execution_stats_test_library
PRIVATE
"${EXECUTORCH_ROOT}/.."
"${EXECUTORCH_ROOT}/examples/arm/arm-scratch/ethos-u/core_software/core_driver/include"
)

add_executable(
ethosu_execution_stats_test
"${EXECUTORCH_ROOT}/backends/arm/runtime/EthosUExecutionStats.cpp"
"${EXECUTORCH_ROOT}/backends/arm/test/ethosu_pmu_test_stubs.cpp"
"${EXECUTORCH_ROOT}/backends/arm/test/ethosu_execution_stats_test.cpp"
)
target_include_directories(
ethosu_execution_stats_test PRIVATE "${EXECUTORCH_ROOT}/.."
ethosu_execution_stats_test
PRIVATE
"${EXECUTORCH_ROOT}/.."
"${EXECUTORCH_ROOT}/examples/arm/arm-scratch/ethos-u/core_software/core_driver/include"
)
target_link_libraries(
ethosu_execution_stats_test PRIVATE ethosu_execution_stats_test_library
Threads::Threads
)
target_link_libraries(ethosu_execution_stats_test PRIVATE Threads::Threads)
add_test(NAME ethosu_execution_stats COMMAND ethosu_execution_stats_test)
set_tests_properties(
ethosu_execution_stats PROPERTIES LABELS "arm;ethos-u;runtime"
)

add_executable(
ethosu_pmu_callbacks_test
"${EXECUTORCH_ROOT}/backends/arm/test/ethosu_pmu_test_stubs.cpp"
"${EXECUTORCH_ROOT}/backends/arm/test/ethosu_pmu_callbacks_test.cpp"
)
target_include_directories(
ethosu_pmu_callbacks_test
PRIVATE
"${EXECUTORCH_ROOT}/.."
"${EXECUTORCH_ROOT}/examples/arm/arm-scratch/ethos-u/core_software/core_driver/include"
)
target_link_libraries(
ethosu_pmu_callbacks_test PRIVATE ethosu_execution_stats_test_library
Threads::Threads
)
add_test(NAME ethosu_pmu_callbacks COMMAND ethosu_pmu_callbacks_test)
set_tests_properties(
ethosu_pmu_callbacks PROPERTIES LABELS "arm;ethos-u;runtime"
)

add_test(
NAME memory_allocation_baseline
COMMAND "${_ethos_u_runtime_tests_dir}/test_memory_allocation_baseline.sh"
Expand Down
Loading
Loading