diff --git a/backends/arm/CMakeLists.txt b/backends/arm/CMakeLists.txt index 2cc8c31790f..69ec45ccd17 100644 --- a/backends/arm/CMakeLists.txt +++ b/backends/arm/CMakeLists.txt @@ -91,12 +91,16 @@ if(EXECUTORCH_BUILD_ARM_BAREMETAL OR EXECUTORCH_BUILD_ARM_ETHOSU_LINUX) ) target_link_libraries(executorch_delegate_ethos_u PUBLIC ethosu_core_driver) add_library( - ethosu_execution_stats STATIC + ethosu_execution_stats STATIC EXCLUDE_FROM_ALL ${EXECUTORCH_ROOT}/backends/arm/runtime/EthosUExecutionStats.cpp ) 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 diff --git a/backends/arm/cmake/ArmRunnerUtils.cmake b/backends/arm/cmake/ArmRunnerUtils.cmake index 81e16079f83..12dc25634ec 100644 --- a/backends/arm/cmake/ArmRunnerUtils.cmake +++ b/backends/arm/cmake/ArmRunnerUtils.cmake @@ -11,6 +11,7 @@ include_guard(DIRECTORY) set(ARM_RUNNER_UTILS_API_COMMANDS arm_runner_add_minimal_executable arm_runner_add_standalone_executorch + arm_runner_check_lock_free_atomics arm_runner_configure_ethos_u_platform arm_runner_configure_linker_script arm_runner_configure_model @@ -35,6 +36,10 @@ set(ARM_RUNNER_UTILS_API_KIND_arm_runner_add_standalone_executorch macro) set(ARM_RUNNER_UTILS_API_SIGNATURE_arm_runner_add_standalone_executorch "arm_runner_add_standalone_executorch()" ) +set(ARM_RUNNER_UTILS_API_KIND_arm_runner_check_lock_free_atomics function) +set(ARM_RUNNER_UTILS_API_SIGNATURE_arm_runner_check_lock_free_atomics + "arm_runner_check_lock_free_atomics(OUT_VAR)" +) set(ARM_RUNNER_UTILS_API_KIND_arm_runner_configure_ethos_u_platform function) set(ARM_RUNNER_UTILS_API_SIGNATURE_arm_runner_configure_ethos_u_platform "arm_runner_configure_ethos_u_platform(*, SDK_PATH, SYSTEM_CONFIG, MEMORY_MODE)" diff --git a/backends/arm/cmake/ArmRunnerUtilsInternal.cmake b/backends/arm/cmake/ArmRunnerUtilsInternal.cmake index f246de33451..ab8e795cb72 100644 --- a/backends/arm/cmake/ArmRunnerUtilsInternal.cmake +++ b/backends/arm/cmake/ArmRunnerUtilsInternal.cmake @@ -373,6 +373,28 @@ function(arm_runner_require_baremetal_targets) endfunction() +function(arm_runner_check_lock_free_atomics OUT_VAR) + if(NOT OUT_VAR) + message(FATAL_ERROR "OUT_VAR is required.") + endif() + + include(CheckCXXSourceCompiles) + # Check the selected CPU again if the build directory is reconfigured. + list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES TARGET_CPU) + unset(_arm_runner_lock_free_atomics CACHE) + check_cxx_source_compiles( + "#include + static_assert(std::atomic::is_always_lock_free); + static_assert(std::atomic::is_always_lock_free); + int main() { return 0; }" + _arm_runner_lock_free_atomics + ) + set(${OUT_VAR} + ${_arm_runner_lock_free_atomics} + PARENT_SCOPE + ) +endfunction() + function(_arm_runner_verify_targets_exist) cmake_parse_arguments(ARG "" "CONTEXT" "TARGETS" ${ARGN}) diff --git a/backends/arm/public_api_manifests/api_manifest_running.toml b/backends/arm/public_api_manifests/api_manifest_running.toml index 044c90b77c5..7c767d2af51 100644 --- a/backends/arm/public_api_manifests/api_manifest_running.toml +++ b/backends/arm/public_api_manifests/api_manifest_running.toml @@ -238,6 +238,10 @@ signature = "arm_runner_add_minimal_executable(*, TARGET, SOURCE, OPS_PREFIX, CO kind = "macro" signature = "arm_runner_add_standalone_executorch()" +[cmake.arm_runner_check_lock_free_atomics] +kind = "function" +signature = "arm_runner_check_lock_free_atomics(OUT_VAR)" + [cmake.arm_runner_configure_ethos_u_platform] kind = "function" signature = "arm_runner_configure_ethos_u_platform(*, SDK_PATH, SYSTEM_CONFIG, MEMORY_MODE)" diff --git a/backends/arm/runtime/EthosUBackend.cpp b/backends/arm/runtime/EthosUBackend.cpp index 7c45905a3de..3fac2009d15 100644 --- a/backends/arm/runtime/EthosUBackend.cpp +++ b/backends/arm/runtime/EthosUBackend.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -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; diff --git a/backends/arm/runtime/EthosUBackend_CoreDriver.cpp b/backends/arm/runtime/EthosUBackend_CoreDriver.cpp index 8be145b9ad7..5d905365329 100644 --- a/backends/arm/runtime/EthosUBackend_CoreDriver.cpp +++ b/backends/arm/runtime/EthosUBackend_CoreDriver.cpp @@ -17,6 +17,7 @@ #include #include +#include #include using executorch::runtime::BackendExecutionContext; @@ -133,6 +134,9 @@ Error platform_execute( handles.scratch_data_size, ethosu_fast_scratch_size}; #endif + executorch::backends::arm::EthosUInvocationContext invocation_context{}; + invocation_context.delegate_handle = + reinterpret_cast(execution_handle); int result = ethosu_invoke_v3( driver.get(), static_cast(handles.cmd_data), @@ -140,7 +144,7 @@ Error platform_execute( 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); diff --git a/backends/arm/runtime/EthosUBackend_Pmu.h b/backends/arm/runtime/EthosUBackend_Pmu.h new file mode 100644 index 00000000000..b80098235c6 --- /dev/null +++ b/backends/arm/runtime/EthosUBackend_Pmu.h @@ -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 + +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 diff --git a/backends/arm/runtime/EthosUExecutionStats.cpp b/backends/arm/runtime/EthosUExecutionStats.cpp index dfa6e801301..92225bea937 100644 --- a/backends/arm/runtime/EthosUExecutionStats.cpp +++ b/backends/arm/runtime/EthosUExecutionStats.cpp @@ -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 + +#include +#include + +#include #include + +namespace executorch::backends::arm { +namespace { +std::atomic 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(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(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(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(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(); +} +} diff --git a/backends/arm/runtime/EthosUExecutionStats.h b/backends/arm/runtime/EthosUExecutionStats.h index 98e6d255dae..fa04759f237 100644 --- a/backends/arm/runtime/EthosUExecutionStats.h +++ b/backends/arm/runtime/EthosUExecutionStats.h @@ -11,6 +11,12 @@ #include #include +// 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. @@ -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 { diff --git a/backends/arm/runtime/targets.bzl b/backends/arm/runtime/targets.bzl index 519595769d9..8ddabe90ade 100644 --- a/backends/arm/runtime/targets.bzl +++ b/backends/arm/runtime/targets.bzl @@ -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"], @@ -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", diff --git a/backends/arm/runtime/tests/ethos-u/CMakeLists.txt b/backends/arm/runtime/tests/ethos-u/CMakeLists.txt index cf3ca5969c1..5a2ee4240e9 100644 --- a/backends/arm/runtime/tests/ethos-u/CMakeLists.txt +++ b/backends/arm/runtime/tests/ethos-u/CMakeLists.txt @@ -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" diff --git a/backends/arm/test/ethosu_pmu_callbacks_test.cpp b/backends/arm/test/ethosu_pmu_callbacks_test.cpp new file mode 100644 index 00000000000..f984be616c7 --- /dev/null +++ b/backends/arm/test/ethosu_pmu_callbacks_test.cpp @@ -0,0 +1,116 @@ +/* + * 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. + */ + +#include +#include + +#include +#include + +#include "ethosu_pmu_test_stubs.h" + +#include +#include +#include + +#define CHECK(expr) \ + do { \ + if (!(expr)) { \ + std::fprintf(stderr, "check failed at line %d: %s\n", __LINE__, #expr); \ + std::abort(); \ + } \ + } while (false) + +using namespace executorch::backends::arm; + +extern "C" { +void ethosu_inference_begin(ethosu_driver*, void*); +void ethosu_inference_end(ethosu_driver*, void*); +} + +int main() { + EthosUExecutionRecord records[2]; + EthosUStatsBuffer buffer{records, 2}; + CHECK(set_ethosu_stats_buffer(&buffer) == nullptr); + CHECK(executorch::backends::arm::ethosu_execution_metrics_enabled()); + + MockPmu u55_pmu; + ethosu_device_config u55_config{&u55_pmu}; + ethosu_driver u55{}; + u55.dev.config = &u55_config; + u55.dev.reg = reinterpret_cast(0x40000000); + u55.dev.caps = {0, 7, 0}; + u55.job.result = ETHOSU_JOB_RESULT_OK; + + MockPmu u85_pmu; + ethosu_device_config u85_config{&u85_pmu}; + ethosu_driver u85{}; + u85.dev.config = &u85_config; + u85.dev.reg = reinterpret_cast(0x50000000); + u85.dev.caps = {2, 8, 0}; + u85.job.result = ETHOSU_JOB_RESULT_ERROR; + + auto invoke = [&buffer](ethosu_driver* driver, uint64_t handle) { + EthosUInvocationContext context{}; + context.delegate_handle = handle; + ethosu_inference_begin(driver, &context); + CHECK(context.buffer == &buffer); + CHECK(pmu(driver).enabled); + pmu(driver).cycles = 100 + driver->dev.caps.product; + pmu(driver).counts[0] = 42 + driver->dev.caps.product; + pmu(driver).overflow = 1; + ethosu_inference_end(driver, &context); + CHECK(!pmu(driver).enabled); + }; + + std::thread first([&] { invoke(&u55, 10); }); + std::thread second([&] { invoke(&u85, 20); }); + first.join(); + second.join(); + + CHECK(buffer.size == 2); + CHECK(!buffer.overflow); + CHECK(observer_begins == 2); + CHECK(observer_ends == 2); + for (size_t index = 0; index < buffer.size; ++index) { + const auto& record = records[index]; + CHECK(record.stats.platform == EthosUPlatform::CoreDriver); + CHECK(record.stats.counter_count == (record.stats.product == 2 ? 7u : 4u)); + CHECK(record.stats.counters[0].overflow); + CHECK(record.stats.counters[0].value == 42 + record.stats.product); + if (record.stats.product == 2) { + CHECK(record.delegate_handle == 20); + CHECK(record.stats.device_identifier == 0x50000000); + CHECK(record.stats.counters[5].event_id == ETHOSU_PMU_MAC_ACTIVE); + CHECK(!record.success); + } else { + CHECK(record.delegate_handle == 10); + CHECK(record.stats.device_identifier == 0x40000000); + CHECK(record.stats.counters[3].event_id == ETHOSU_PMU_NPU_IDLE); + CHECK(record.success); + } + } + + MockPmu unknown_pmu; + ethosu_device_config unknown_config{&unknown_pmu}; + ethosu_driver unknown{}; + unknown.dev.config = &unknown_config; + unknown.dev.caps = {99, 8, 0}; + EthosUInvocationContext reused_context{}; + reused_context.counter_count = 32; + ethosu_inference_begin(&unknown, &reused_context); + CHECK(reused_context.buffer == &buffer); + CHECK(reused_context.counter_count == 0); + CHECK(!unknown_pmu.enabled); + ethosu_inference_end(&unknown, &reused_context); + CHECK(buffer.size == 2); + CHECK(observer_begins == 3); + CHECK(observer_ends == 3); + + CHECK(set_ethosu_stats_buffer(nullptr) == &buffer); + std::puts("Ethos-U PMU callbacks: PASS"); +} diff --git a/backends/arm/test/ethosu_pmu_test_stubs.cpp b/backends/arm/test/ethosu_pmu_test_stubs.cpp new file mode 100644 index 00000000000..144a45e08e6 --- /dev/null +++ b/backends/arm/test/ethosu_pmu_test_stubs.cpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "ethosu_pmu_test_stubs.h" + +#include +#include + +MockPmu& pmu(ethosu_driver* driver) { + return *static_cast(driver->dev.config->config); +} + +std::atomic observer_begins{0}; +std::atomic observer_ends{0}; + +extern "C" { +// Match the driver's weak hooks so the test verifies archive override behavior. +void __attribute__((weak)) ethosu_inference_begin(ethosu_driver*, void*) {} +void __attribute__((weak)) ethosu_inference_end(ethosu_driver*, void*) {} + +void EthosUBackend_npu_begin() { + ++observer_begins; +} + +void EthosUBackend_npu_end() { + ++observer_ends; +} + +void ETHOSU_PMU_Enable(ethosu_driver* driver) { + pmu(driver).enabled = true; +} + +void ETHOSU_PMU_Disable(ethosu_driver* driver) { + pmu(driver).enabled = false; +} + +uint32_t ETHOSU_PMU_Get_NumEventCountersForDrv(ethosu_driver* driver) { + return driver->dev.caps.product == 2 ? 8 : 4; +} + +void ETHOSU_PMU_Set_EVTYPER( + ethosu_driver* driver, + uint32_t slot, + ethosu_pmu_event_type event) { + pmu(driver).events[slot] = event; +} + +ethosu_pmu_event_type ETHOSU_PMU_Get_EVTYPER( + ethosu_driver* driver, + uint32_t slot) { + return pmu(driver).events[slot]; +} + +void ETHOSU_PMU_PMCCNTR_CFG_Set_Stop_Event( + ethosu_driver*, + ethosu_pmu_event_type) {} +void ETHOSU_PMU_PMCCNTR_CFG_Set_Start_Event( + ethosu_driver*, + ethosu_pmu_event_type) {} + +void ETHOSU_PMU_CNTR_Enable(ethosu_driver* driver, uint32_t mask) { + pmu(driver).enabled_mask = mask; +} + +void ETHOSU_PMU_CYCCNT_Reset(ethosu_driver* driver) { + pmu(driver).cycles = 0; +} + +void ETHOSU_PMU_EVCNTR_ALL_Reset(ethosu_driver* driver) { + std::fill(std::begin(pmu(driver).counts), std::end(pmu(driver).counts), 0); +} + +void ETHOSU_PMU_Set_CNTR_OVS(ethosu_driver* driver, uint32_t mask) { + pmu(driver).overflow &= ~mask; +} + +uint32_t ETHOSU_PMU_Get_CNTR_OVS(ethosu_driver* driver) { + return pmu(driver).overflow; +} + +uint64_t ETHOSU_PMU_Get_CCNTR(ethosu_driver* driver) { + return pmu(driver).cycles; +} + +uint32_t ETHOSU_PMU_Get_EVCNTR(ethosu_driver* driver, uint32_t slot) { + return pmu(driver).counts[slot]; +} +} diff --git a/backends/arm/test/ethosu_pmu_test_stubs.h b/backends/arm/test/ethosu_pmu_test_stubs.h new file mode 100644 index 00000000000..70045a88816 --- /dev/null +++ b/backends/arm/test/ethosu_pmu_test_stubs.h @@ -0,0 +1,25 @@ +/* + * 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 +#include + +#include + +struct MockPmu { + ethosu_pmu_event_type events[8]{}; + uint32_t counts[8]{}; + uint64_t cycles = 0; + uint32_t overflow = 0; + uint32_t enabled_mask = 0; + bool enabled = false; +}; + +MockPmu& pmu(ethosu_driver* driver); +extern std::atomic observer_begins; +extern std::atomic observer_ends; diff --git a/backends/arm/test/test_arm_backend.sh b/backends/arm/test/test_arm_backend.sh index ceea63ba8e1..e2ff9d85b29 100755 --- a/backends/arm/test/test_arm_backend.sh +++ b/backends/arm/test/test_arm_backend.sh @@ -287,6 +287,9 @@ test_run_ethos_u55() { examples/arm/run.sh --et_build_root=arm_test/test_run --target=ethos-u55-128 --model_name=examples/arm/example_modules/add.py examples/arm/run.sh --et_build_root=arm_test/test_run --target=ethos-u55-128 --model_name=examples/arm/example_modules/add.py --bundleio + # Verify the runner links and runs without ethosu_execution_stats. + examples/arm/run.sh --et_build_root=arm_test/test_run_no_stats --target=ethos-u55-128 --model_name=add --extra_build_flags="-DEXECUTORCH_ARM_ETHOSU_PMU_STATS=OFF" + echo "${TEST_SUITE_NAME}: Test target Ethos-U65" examples/arm/run.sh --et_build_root=arm_test/test_run --target=ethos-u65-256 --model_name=examples/arm/example_modules/add.py examples/arm/run.sh --et_build_root=arm_test/test_run --target=ethos-u65-256 --model_name=examples/arm/example_modules/add.py --bundleio diff --git a/backends/cortex_m/test/build_test_runner.sh b/backends/cortex_m/test/build_test_runner.sh index d18463059ef..665472234c1 100755 --- a/backends/cortex_m/test/build_test_runner.sh +++ b/backends/cortex_m/test/build_test_runner.sh @@ -83,4 +83,4 @@ ops_list=( cortex_m::quantized_batch_matmul.out ) -${build_executor_runner} --pte=semihosting --bundleio --target="${target}" --output="${build_root_test_dir}" --select_ops_list="$(join_by_comma "${ops_list[@]}")" --extra_build_flags="-DCORTEX_M_ENABLE_RUNTIME_CHECKS=ON -DET_ATOL=5.0 -DET_RTOL=1.0 -DET_ARM_BAREMETAL_SCRATCH_TEMP_ALLOCATOR_POOL_SIZE=0" +${build_executor_runner} --pte=semihosting --bundleio --target="${target}" --output="${build_root_test_dir}" --select_ops_list="$(join_by_comma "${ops_list[@]}")" --extra_build_flags="-DCORTEX_M_ENABLE_RUNTIME_CHECKS=ON -DET_ATOL=5.0 -DET_RTOL=1.0 -DET_ARM_BAREMETAL_SCRATCH_TEMP_ALLOCATOR_POOL_SIZE=0 -DEXECUTORCH_ARM_ETHOSU_PMU_STATS=OFF" diff --git a/examples/arm/executor_runner/CMakeLists.txt b/examples/arm/executor_runner/CMakeLists.txt index 63d49a1ba1e..62832afd7bb 100644 --- a/examples/arm/executor_runner/CMakeLists.txt +++ b/examples/arm/executor_runner/CMakeLists.txt @@ -57,6 +57,10 @@ endif() option(ET_LOG_DUMP_INPUT "Dump input in log" OFF) option(ET_LOG_DUMP_OUTPUT "Dump output in log" ON) +option(EXECUTORCH_ARM_ETHOSU_PMU_STATS + "Link Ethos-U PMU statistics into the Arm executor runner" + ${EXECUTORCH_BUILD_ARM_BAREMETAL} +) option(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING "Report Ethos-U PMU statistics per delegate" OFF ) @@ -67,7 +71,6 @@ set(ET_ARM_ETHOSU_MAX_PROFILED_DELEGATES "16" CACHE STRING "Maximum delegates tracked by per-delegate profiling" ) - option(ET_BUNDLE_IO "Set to compile in BundleIO support" OFF) set(BUNDLED_PROGRAM_LIBRARY_DIR "" @@ -111,6 +114,16 @@ set(ET_ARM_BAREMETAL_PLANNED_FAST_MEMORY_SIZE include(${EXECUTORCH_ROOT}/backends/arm/cmake/ArmRunnerUtils.cmake) +if(EXECUTORCH_ARM_ETHOSU_PMU_STATS) + arm_runner_check_lock_free_atomics(_ethosu_pmu_stats_atomics) + if(NOT _ethosu_pmu_stats_atomics) + message( + FATAL_ERROR + "Ethos-U PMU stats require lock-free byte and pointer atomics, which this target/toolchain does not provide. Disable them with -DEXECUTORCH_ARM_ETHOSU_PMU_STATS=OFF." + ) + endif() +endif() + arm_runner_define_cache_options() arm_runner_validate_model_source(ALLOW_SEMIHOSTING) arm_runner_require_python() @@ -352,6 +365,12 @@ if(ET_NUM_INFERENCES) endif() if(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) + if(NOT EXECUTORCH_ARM_ETHOSU_PMU_STATS) + message( + FATAL_ERROR + "ET_ARM_ETHOSU_PER_DELEGATE_PROFILING requires EXECUTORCH_ARM_ETHOSU_PMU_STATS" + ) + endif() if(NOT ET_ARM_ETHOSU_MAX_PROFILED_DELEGATES MATCHES "^[1-9][0-9]*$") message( FATAL_ERROR @@ -369,6 +388,19 @@ if(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) ) endif() +if(EXECUTORCH_ARM_ETHOSU_PMU_STATS) + if(NOT TARGET ethosu_execution_stats) + message( + FATAL_ERROR + "EXECUTORCH_ARM_ETHOSU_PMU_STATS is currently only supported by the bare-metal Ethos-U backend" + ) + endif() + target_link_libraries(arm_executor_runner PRIVATE ethosu_execution_stats) + target_compile_definitions( + arm_executor_runner PRIVATE ET_ARM_ETHOSU_PMU_STATS + ) +endif() + if(ET_ARM_ETHOSU_PROFILE_IO_COPIES) target_compile_definitions( arm_executor_runner PRIVATE ET_ARM_ETHOSU_PROFILE_IO_COPIES diff --git a/examples/arm/executor_runner/arm_executor_runner.cpp b/examples/arm/executor_runner/arm_executor_runner.cpp index 29940ba47f9..f37844ced65 100644 --- a/examples/arm/executor_runner/arm_executor_runner.cpp +++ b/examples/arm/executor_runner/arm_executor_runner.cpp @@ -1365,6 +1365,7 @@ bool run_model( } } status = ctx.method.value()->execute(); + ConsumeMeasurements(); if (status != Error::Ok) { break; } @@ -1433,6 +1434,7 @@ bool run_model_server( StartMeasurements(); status = ctx.method.value()->execute(); + ConsumeMeasurements(); StopMeasurements(1); ET_CHECK_MSG( status == Error::Ok, diff --git a/examples/arm/executor_runner/arm_perf_monitor.cpp b/examples/arm/executor_runner/arm_perf_monitor.cpp index 9e37df3bb56..c45869b11a4 100644 --- a/examples/arm/executor_runner/arm_perf_monitor.cpp +++ b/examples/arm/executor_runner/arm_perf_monitor.cpp @@ -12,9 +12,11 @@ using printf_size_t = unsigned long; #ifdef ETHOSU -#include +#include #include -#include +#if defined(ET_ARM_ETHOSU_PMU_STATS) +#include +#endif namespace { @@ -27,21 +29,86 @@ static inline uint64_t arm_pmu_cycles() { #endif } -#if defined(ETHOSU55) || defined(ETHOSU65) -const uint32_t ethosu_pmuCountersUsed = 4; -#elif defined(ETHOSU85) -const uint32_t ethosu_pmuCountersUsed = 7; -#else -#error No NPU target defined -#endif - uint32_t ethosu_delegation_count = 0; uint64_t ethosu_ArmCycleCountStart = 0; uint64_t ethosu_ArmBackendExecuteCycleCountStart = 0; uint64_t ethosu_ArmBackendExecuteCycleCount = 0; uint64_t ethosu_ArmWhenNPURunCycleCountStart = 0; uint64_t ethosu_ArmWhenNPURunCycleCount = 0; + +#if defined(ET_ARM_ETHOSU_PMU_STATS) +using executorch::backends::arm::EthosUExecutionRecord; +using executorch::backends::arm::EthosUExecutionStats; +using executorch::backends::arm::EthosUStatsBuffer; + +constexpr size_t kMaxPmuRecordsPerInference = 16; +constexpr size_t kMaxProfiledDevices = 1; + +std::array ethosu_pmuRecords; +EthosUStatsBuffer ethosu_pmuBuffer{ + ethosu_pmuRecords.data(), + ethosu_pmuRecords.size()}; + +struct DevicePmuTotals { + EthosUExecutionStats stats{}; + uint64_t invocations = 0; + uint64_t failures = 0; +}; +std::array ethosu_deviceTotals; +size_t ethosu_deviceCount = 0; +bool ethosu_deviceCapacityExceeded = false; +bool ethosu_recordCapacityExceeded = false; uint64_t ethosu_pmuCycleCount = 0; +#if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) +struct DelegatePmuTotals { + uintptr_t handle = 0; + DevicePmuTotals totals; +}; +std::array + ethosu_delegatePmuTotals; +size_t ethosu_delegatePmuCount = 0; +bool ethosu_delegatePmuCapacityExceeded = false; +#endif + +bool same_layout( + const EthosUExecutionStats& left, + const EthosUExecutionStats& right) { + if (left.device_identifier != right.device_identifier || + left.product != right.product || left.platform != right.platform || + left.counter_count != right.counter_count) { + return false; + } + for (uint32_t slot = 0; slot < left.counter_count; ++slot) { + if (left.counters[slot].event_id != right.counters[slot].event_id) { + return false; + } + } + return true; +} + +void accumulate_pmu_totals( + DevicePmuTotals& totals, + const EthosUExecutionRecord& record) { + if (totals.invocations == 0) { + totals.stats = record.stats; + totals.stats.cycle_count = 0; + totals.stats.cycle_overflow = false; + for (uint32_t slot = 0; slot < totals.stats.counter_count; ++slot) { + totals.stats.counters[slot].value = 0; + totals.stats.counters[slot].overflow = false; + } + } + ++totals.invocations; + totals.failures += !record.success; + totals.stats.cycle_count += record.stats.cycle_count; + totals.stats.cycle_overflow |= record.stats.cycle_overflow; + for (uint32_t slot = 0; slot < record.stats.counter_count; ++slot) { + totals.stats.counters[slot].value += record.stats.counters[slot].value; + totals.stats.counters[slot].overflow |= + record.stats.counters[slot].overflow; + } +} +#endif #if defined(ET_ARM_ETHOSU_PROFILE_IO_COPIES) struct IOCopyStats { uint64_t calls = 0; @@ -56,7 +123,6 @@ struct DelegateStats { uint64_t backend_invocations = 0; uint64_t npu_invocations = 0; uint64_t pmu_cycles = 0; - std::array pmu_events{}; #if defined(ET_ARM_ETHOSU_PROFILE_IO_COPIES) IOCopyStats input_copies; IOCopyStats output_copies; @@ -84,12 +150,6 @@ DelegateStats* get_delegate_stats(const void* handle) { return &stats; } #endif -std::array ethosu_pmuEventCounts = {0}; - -// ethosu_pmuCountersUsed should match numbers of counters setup in -// ethosu_inference_begin() and not be more then the HW supports -static_assert(ETHOSU_PMU_NCOUNTERS >= ethosu_pmuCountersUsed); - } // namespace extern "C" { @@ -131,82 +191,14 @@ void EthosUBackend_delegate_end() { } #endif -// Callback invoked at start of NPU execution -void ethosu_inference_begin(struct ethosu_driver* drv, void*) { - // Enable PMU - 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); - - // Setup 4 counters -#if defined(ETHOSU55) || defined(ETHOSU65) - ETHOSU_PMU_Set_EVTYPER(drv, 0, ETHOSU_PMU_AXI0_RD_DATA_BEAT_RECEIVED); - ETHOSU_PMU_Set_EVTYPER(drv, 1, ETHOSU_PMU_AXI1_RD_DATA_BEAT_RECEIVED); - ETHOSU_PMU_Set_EVTYPER(drv, 2, ETHOSU_PMU_AXI0_WR_DATA_BEAT_WRITTEN); - ETHOSU_PMU_Set_EVTYPER(drv, 3, ETHOSU_PMU_NPU_IDLE); - // Enable the 4 counters - ETHOSU_PMU_CNTR_Enable( - drv, - ETHOSU_PMU_CNT1_Msk | ETHOSU_PMU_CNT2_Msk | ETHOSU_PMU_CNT3_Msk | - ETHOSU_PMU_CNT4_Msk); -#elif defined(ETHOSU85) - ETHOSU_PMU_Set_EVTYPER(drv, 0, ETHOSU_PMU_SRAM_RD_DATA_BEAT_RECEIVED); - ETHOSU_PMU_Set_EVTYPER(drv, 1, ETHOSU_PMU_SRAM_WR_DATA_BEAT_WRITTEN); - ETHOSU_PMU_Set_EVTYPER(drv, 2, ETHOSU_PMU_EXT_RD_DATA_BEAT_RECEIVED); - ETHOSU_PMU_Set_EVTYPER(drv, 3, ETHOSU_PMU_EXT_WR_DATA_BEAT_WRITTEN); - ETHOSU_PMU_Set_EVTYPER(drv, 4, ETHOSU_PMU_NPU_IDLE); - ETHOSU_PMU_Set_EVTYPER(drv, 5, ETHOSU_PMU_MAC_ACTIVE); - ETHOSU_PMU_Set_EVTYPER(drv, 6, ETHOSU_PMU_WD_ACTIVE); - // Enable the 7 counters - ETHOSU_PMU_CNTR_Enable( - drv, - ETHOSU_PMU_CNT1_Msk | ETHOSU_PMU_CNT2_Msk | ETHOSU_PMU_CNT3_Msk | - ETHOSU_PMU_CNT4_Msk | ETHOSU_PMU_CNT5_Msk | ETHOSU_PMU_CNT6_Msk | - ETHOSU_PMU_CNT7_Msk); -#else -#error No NPU target defined -#endif - - ETHOSU_PMU_CNTR_Enable(drv, ETHOSU_PMU_CCNT_Msk); - ETHOSU_PMU_CYCCNT_Reset(drv); - - // Reset all counters - ETHOSU_PMU_EVCNTR_ALL_Reset(drv); - - // Save Cortex-M cycle clock to calculate total CPU cycles used in - // ethosu_inference_end() +void EthosUBackend_npu_begin() { ethosu_ArmWhenNPURunCycleCountStart = arm_pmu_cycles(); } -// Callback invoked at end of NPU execution -void ethosu_inference_end(struct ethosu_driver* drv, void*) { - ethosu_delegation_count++; -#if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) - const uint64_t pmu_cycles = ETHOSU_PMU_Get_CCNTR(drv); - ethosu_pmuCycleCount += pmu_cycles; - if (ethosu_activeDelegate != nullptr) { - ethosu_activeDelegate->npu_invocations++; - ethosu_activeDelegate->pmu_cycles += pmu_cycles; - } -#else - ethosu_pmuCycleCount += ETHOSU_PMU_Get_CCNTR(drv); -#endif - - for (size_t i = 0; i < ethosu_pmuCountersUsed; i++) { -#if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) - const uint64_t event_count = ETHOSU_PMU_Get_EVCNTR(drv, i); - ethosu_pmuEventCounts[i] += event_count; - if (ethosu_activeDelegate != nullptr) { - ethosu_activeDelegate->pmu_events[i] += event_count; - } -#else - ethosu_pmuEventCounts[i] += ETHOSU_PMU_Get_EVCNTR(drv, i); -#endif - } - ETHOSU_PMU_Disable(drv); - // Add Cortex-M cycle clock used during this NPU execution +void EthosUBackend_npu_end() { ethosu_ArmWhenNPURunCycleCount += - (arm_pmu_cycles() - ethosu_ArmWhenNPURunCycleCountStart); + arm_pmu_cycles() - ethosu_ArmWhenNPURunCycleCountStart; + ++ethosu_delegation_count; } // Callback invoked at start of ArmBackend::execute() @@ -224,6 +216,57 @@ void EthosUBackend_execute_end() { } } +void ConsumeMeasurements() { +#if defined(ET_ARM_ETHOSU_PMU_STATS) + for (size_t index = 0; index < ethosu_pmuBuffer.size; ++index) { + const EthosUExecutionRecord& record = ethosu_pmuBuffer.records[index]; + ethosu_pmuCycleCount += record.stats.cycle_count; +#if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) + if (auto* delegate = get_delegate_stats( + reinterpret_cast(record.delegate_handle))) { + ++delegate->npu_invocations; + delegate->pmu_cycles += record.stats.cycle_count; + } + DelegatePmuTotals* delegate_totals = nullptr; + for (size_t entry = 0; entry < ethosu_delegatePmuCount; ++entry) { + auto& candidate = ethosu_delegatePmuTotals[entry]; + if (candidate.handle == record.delegate_handle && + same_layout(candidate.totals.stats, record.stats)) { + delegate_totals = &candidate; + break; + } + } + if (delegate_totals == nullptr && + ethosu_delegatePmuCount < ethosu_delegatePmuTotals.size()) { + delegate_totals = ðosu_delegatePmuTotals[ethosu_delegatePmuCount++]; + delegate_totals->handle = record.delegate_handle; + } + if (delegate_totals != nullptr) { + accumulate_pmu_totals(delegate_totals->totals, record); + } else { + ethosu_delegatePmuCapacityExceeded = true; + } +#endif + DevicePmuTotals* totals = nullptr; + for (size_t device = 0; device < ethosu_deviceCount; ++device) { + if (same_layout(ethosu_deviceTotals[device].stats, record.stats)) { + totals = ðosu_deviceTotals[device]; + break; + } + } + if (totals == nullptr && ethosu_deviceCount < ethosu_deviceTotals.size()) { + totals = ðosu_deviceTotals[ethosu_deviceCount++]; + } + if (totals == nullptr) { + ethosu_deviceCapacityExceeded = true; + continue; + } + accumulate_pmu_totals(*totals, record); + } + ethosu_recordCapacityExceeded |= ethosu_pmuBuffer.reset(); +#endif +} + void StartMeasurements() { #if defined(__ARM_ARCH_8_1M_MAIN__) // StopMeasurements() disables the cycle counter after each measurement. @@ -233,8 +276,22 @@ void StartMeasurements() { #endif ethosu_delegation_count = 0; ethosu_ArmBackendExecuteCycleCount = 0; + ethosu_ArmWhenNPURunCycleCountStart = 0; ethosu_ArmWhenNPURunCycleCount = 0; +#if defined(ET_ARM_ETHOSU_PMU_STATS) + ethosu_deviceTotals = {}; + ethosu_deviceCount = 0; + ethosu_deviceCapacityExceeded = false; + ethosu_recordCapacityExceeded = false; ethosu_pmuCycleCount = 0; +#if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) + ethosu_delegatePmuTotals = {}; + ethosu_delegatePmuCount = 0; + ethosu_delegatePmuCapacityExceeded = false; +#endif + ethosu_pmuBuffer.reset(); + executorch::backends::arm::set_ethosu_stats_buffer(ðosu_pmuBuffer); +#endif #if defined(ET_ARM_ETHOSU_PROFILE_IO_COPIES) ethosu_inputCopyStats = {}; ethosu_outputCopyStats = {}; @@ -246,9 +303,6 @@ void StartMeasurements() { ethosu_delegateCapacityExceeded = false; #endif - for (size_t i = 0; i < ethosu_pmuCountersUsed; i++) { - ethosu_pmuEventCounts[i] = 0; - } ethosu_ArmCycleCountStart = arm_pmu_cycles(); } @@ -262,6 +316,12 @@ void StopMeasurements(size_t num_inferences) { ET_LOG(Error, "Cannot report measurements without completed inferences"); return; } + if (!executorch::backends::arm::ethosu_execution_metrics_enabled()) { + ET_LOG( + Info, + "WARNING: Ethos-U execution metrics are not linked; NPU delegations, " + "wait cycles, and CPU/NPU ratios are incorrect"); + } uint64_t cycle_count = arm_pmu_cycles() - ethosu_ArmCycleCountStart; // Number of comand streams handled by the NPU @@ -351,21 +411,50 @@ void StopMeasurements(size_t num_inferences) { ethosu_ArmWhenNPURunCycleCount, (double)ethosu_ArmWhenNPURunCycleCount / num_inferences); +#if defined(ET_ARM_ETHOSU_PMU_STATS) ET_LOG(Info, "Ethos-U PMU report:"); + for (size_t device = 0; device < ethosu_deviceCount; ++device) { + const DevicePmuTotals& totals = ethosu_deviceTotals[device]; + const EthosUExecutionStats& stats = totals.stats; + ET_LOG( + Info, + "Ethos-U NPU 0x%" PRIxPTR ": product %" PRIu32 ", invocations %" PRIu64 + ", failures %" PRIu64, + stats.device_identifier, + stats.product, + totals.invocations, + totals.failures); + ET_LOG( + Info, + "Ethos-U NPU 0x%" PRIxPTR " PMU cycles: %" PRIu64 " (overflow: %u)", + stats.device_identifier, + stats.cycle_count, + static_cast(stats.cycle_overflow)); + for (uint32_t slot = 0; slot < stats.counter_count; ++slot) { + const auto& counter = stats.counters[slot]; + ET_LOG( + Info, + "Ethos-U NPU 0x%" PRIxPTR " PMU counter %" PRIu32 " event %" PRIu32 + ": %" PRIu64 " (overflow: %u)", + stats.device_identifier, + slot, + counter.event_id, + counter.value, + static_cast(counter.overflow)); + } + } ET_LOG( Info, "ethosu_pmu_cycle_cntr : %" PRIu64 " (%.2f per inference)", ethosu_pmuCycleCount, (double)ethosu_pmuCycleCount / num_inferences); - - for (size_t i = 0; i < ethosu_pmuCountersUsed; i++) { - ET_LOG( - Info, - "ethosu_pmu_cntr%lu : %" PRIu64 " (%.2f per inference)", - static_cast(i), - ethosu_pmuEventCounts[i], - (double)ethosu_pmuEventCounts[i] / num_inferences); + if (ethosu_recordCapacityExceeded) { + ET_LOG(Error, "Ethos-U PMU record buffer overflowed; samples were lost"); + } + if (ethosu_deviceCapacityExceeded) { + ET_LOG(Error, "Ethos-U PMU device/layout capacity exceeded"); } +#endif #if defined(ET_ARM_ETHOSU_PER_DELEGATE_PROFILING) ET_LOG(Info, "Ethos-U per-delegate PMU report:"); for (size_t delegate_id = 0; delegate_id < ethosu_delegateCount; @@ -383,13 +472,25 @@ void StopMeasurements(size_t num_inferences) { "Ethos-U delegate %lu PMU cycles: %" PRIu64, static_cast(delegate_id), stats.pmu_cycles); - for (size_t event = 0; event < ethosu_pmuCountersUsed; ++event) { - ET_LOG( - Info, - "Ethos-U delegate %lu PMU counter %lu: %" PRIu64, - static_cast(delegate_id), - static_cast(event), - stats.pmu_events[event]); + for (size_t entry = 0; entry < ethosu_delegatePmuCount; ++entry) { + const DelegatePmuTotals& pmu_totals = ethosu_delegatePmuTotals[entry]; + if (pmu_totals.handle != reinterpret_cast(stats.handle)) { + continue; + } + const EthosUExecutionStats& pmu = pmu_totals.totals.stats; + for (uint32_t slot = 0; slot < pmu.counter_count; ++slot) { + const auto& counter = pmu.counters[slot]; + ET_LOG( + Info, + "Ethos-U delegate %lu NPU 0x%" PRIxPTR " PMU counter %" PRIu32 + " event %" PRIu32 ": %" PRIu64 " (overflow: %u)", + static_cast(delegate_id), + pmu.device_identifier, + slot, + counter.event_id, + counter.value, + static_cast(counter.overflow)); + } } #if defined(ET_ARM_ETHOSU_PROFILE_IO_COPIES) ET_LOG( @@ -413,17 +514,9 @@ void StopMeasurements(size_t num_inferences) { "Ethos-U per-delegate profiling exceeded its capacity of %lu delegates", static_cast(ethosu_delegateStats.size())); } -#endif -#if defined(ETHOSU55) || defined(ETHOSU65) - ET_LOG( - Info, - "Ethos-U PMU Events:[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]"); -#elif defined(ETHOSU85) - ET_LOG( - Info, - "Ethos-U PMU Events:[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]"); -#else -#error No NPU target defined + if (ethosu_delegatePmuCapacityExceeded) { + ET_LOG(Error, "Ethos-U per-delegate PMU device/layout capacity exceeded"); + } #endif } @@ -431,6 +524,9 @@ void StopMeasurements(size_t num_inferences) { // cppcheck-suppress unusedFunction void StartMeasurements() {} +// cppcheck-suppress unusedFunction +void ConsumeMeasurements() {} + // cppcheck-suppress unusedFunction void StopMeasurements(size_t num_inferences) { (void)num_inferences; diff --git a/examples/arm/executor_runner/arm_perf_monitor.h b/examples/arm/executor_runner/arm_perf_monitor.h index ef37e25fd6a..1a6173af240 100644 --- a/examples/arm/executor_runner/arm_perf_monitor.h +++ b/examples/arm/executor_runner/arm_perf_monitor.h @@ -9,4 +9,5 @@ #include void StartMeasurements(); +void ConsumeMeasurements(); void StopMeasurements(size_t num_inferences); diff --git a/examples/arm/executor_runner/standalone/CMakeLists.txt b/examples/arm/executor_runner/standalone/CMakeLists.txt index 963f8908259..01ae41c6a30 100644 --- a/examples/arm/executor_runner/standalone/CMakeLists.txt +++ b/examples/arm/executor_runner/standalone/CMakeLists.txt @@ -87,7 +87,6 @@ set(EXECUTORCH_SKIP_ARM_EXECUTOR_RUNNER ON CACHE BOOL "" FORCE ) - # examples/arm/executor_runner/CMakeLists.txt generates runner-specific # registration libraries based on the PTE or an explicit select-ops list. Avoid # feeding those cache entries into the top-level ExecuTorch configure, otherwise