Skip to content
Merged
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
7 changes: 7 additions & 0 deletions backends/arm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ if(EXECUTORCH_BUILD_ARM_BAREMETAL OR EXECUTORCH_BUILD_ARM_ETHOSU_LINUX)
executorch_delegate_ethos_u PRIVATE ${DRIVER_ETHOSU_INCLUDE_DIR}
)
target_link_libraries(executorch_delegate_ethos_u PUBLIC ethosu_core_driver)
add_library(
ethosu_execution_stats STATIC
${EXECUTORCH_ROOT}/backends/arm/runtime/EthosUExecutionStats.cpp
)
target_include_directories(
ethosu_execution_stats PRIVATE ${_common_include_directories}
)
elseif(EXECUTORCH_BUILD_ARM_ETHOSU_LINUX)
target_sources(
executorch_delegate_ethos_u
Expand Down
7 changes: 7 additions & 0 deletions backends/arm/runtime/EthosUExecutionStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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 <executorch/backends/arm/runtime/EthosUExecutionStats.h>
114 changes: 114 additions & 0 deletions backends/arm/runtime/EthosUExecutionStats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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 <array>
#include <atomic>
#include <cstddef>
#include <cstdint>

namespace executorch::backends::arm {

// Containers of Ethos-U metrics.
// An EthosUStatsBuffer contains many EthosUExecutionRecords.
// An EthosUExecutionRecord contains delegate id and status, and an
// EthosUExecutionStats, containing Ethos-U specific metrics.
struct EthosUExecutionStats;
struct EthosUExecutionRecord;
struct EthosUStatsBuffer;

constexpr size_t kMaxPmuEventCounters = 8;

enum class EthosUPlatform : uint8_t {
CoreDriver,
};

struct EthosUPmuCounter {
uint32_t event_id = 0;
uint64_t value = 0;
bool overflow = false;
};

struct EthosUExecutionStats {
// Register base address of the physical NPU that produced this record.
uintptr_t device_identifier = 0;
// Ethos-U product identifier for the NPU that produced this record.
uint32_t product = 0;
// Platform that produced this record.
EthosUPlatform platform = EthosUPlatform::CoreDriver;
// NPU PMU cycle count for this execution, when available.
uint64_t cycle_count = 0;
// Whether cycle_count was measured; zero can be a valid measurement.
bool cycle_count_valid = false;
// Whether the cycle counter overflowed, if overflow status is available.
bool cycle_overflow = false;
// Number of populated entries at the start of counters.
uint32_t counter_count = 0;
// PMU event IDs, counts, and overflow indications for this execution.
std::array<EthosUPmuCounter, kMaxPmuEventCounters> counters{};

void reset() {
*this = {};
}
};

struct EthosUExecutionRecord {
EthosUExecutionStats stats;
uintptr_t delegate_handle = 0;
bool success = false;
};

// Appends from concurrent invocations are synchronized. Read the records only
// after those invocations have completed.
struct EthosUStatsBuffer {
// Records are added at the start of the supplied array.
EthosUExecutionRecord* records = nullptr;
size_t capacity = 0;
size_t size = 0;
bool overflow = false;

constexpr EthosUStatsBuffer() = default;
constexpr EthosUStatsBuffer(
EthosUExecutionRecord* records,
size_t capacity,
bool overflow = false) noexcept
: records(records), capacity(capacity), overflow(overflow) {}

void append(const EthosUExecutionRecord& record) noexcept {
lock();
if (size >= capacity) {
overflow = true;
} else {
records[size++] = record;
}
unlock();
}

// Read records or reset this buffer only after all publishers have completed.
bool reset() noexcept {
lock();
const bool had_overflow = overflow;
size = 0;
overflow = false;
unlock();
return had_overflow;
}

private:
void lock() noexcept {
while (append_lock_.test_and_set(std::memory_order_acquire)) {
}
}

void unlock() noexcept {
append_lock_.clear(std::memory_order_release);
}

std::atomic_flag append_lock_ = ATOMIC_FLAG_INIT;
};

} // namespace executorch::backends::arm
15 changes: 15 additions & 0 deletions backends/arm/runtime/tests/ethos-u/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ set_tests_properties(
vela_external_blocks PROPERTIES LABELS "arm;ethos-u;runtime"
)

find_package(Threads REQUIRED)
add_executable(
ethosu_execution_stats_test
"${EXECUTORCH_ROOT}/backends/arm/runtime/EthosUExecutionStats.cpp"
"${EXECUTORCH_ROOT}/backends/arm/test/ethosu_execution_stats_test.cpp"
)
target_include_directories(
ethosu_execution_stats_test PRIVATE "${EXECUTORCH_ROOT}/.."
)
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_test(
NAME memory_allocation_baseline
COMMAND "${_ethos_u_runtime_tests_dir}/test_memory_allocation_baseline.sh"
Expand Down
103 changes: 103 additions & 0 deletions backends/arm/test/ethosu_execution_stats_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* 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 <executorch/backends/arm/runtime/EthosUExecutionStats.h>

#include <cstdio>
#include <cstdlib>
#include <thread>

#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;

int main() {
EthosUExecutionRecord records[2];
EthosUStatsBuffer buffer{records, 2};
EthosUExecutionRecord u55{};
u55.delegate_handle = 10;
u55.success = true;
u55.stats.device_identifier = 0x40000000;
u55.stats.product = 0;
u55.stats.cycle_count = 100;
u55.stats.cycle_count_valid = true;
u55.stats.counter_count = 4;
u55.stats.counters[0] = {17, 42, false};

buffer.append(u55);
CHECK(buffer.size == 1);

EthosUExecutionRecord u85{};
u85.delegate_handle = 20;
u85.stats.device_identifier = 0x50000000;
u85.stats.product = 2;
u85.stats.counter_count = 8;
u85.stats.counters[7] = {22, 99, false};

buffer.append(u85);
buffer.append(u55);
CHECK(buffer.size == 2 && buffer.overflow);
CHECK(records[0].stats.device_identifier == 0x40000000);
CHECK(records[0].stats.counter_count == 4);
CHECK(records[0].stats.counters[0].event_id == 17);
CHECK(records[1].stats.device_identifier == 0x50000000);
CHECK(records[1].stats.platform == EthosUPlatform::CoreDriver);
CHECK(records[1].stats.counter_count == 8);
CHECK(records[1].stats.counters[7].value == 99);
CHECK(!records[1].success);
CHECK(buffer.reset());
CHECK(buffer.size == 0 && !buffer.overflow);
buffer.append(u55);
CHECK(buffer.size == 1);
CHECK(!buffer.reset());

constexpr size_t kThreads = 4;
constexpr size_t kRecordsPerThread = 8;
EthosUExecutionRecord concurrent_records[kThreads * kRecordsPerThread];
EthosUStatsBuffer concurrent_buffer{
concurrent_records, kThreads * kRecordsPerThread};
std::thread publishers[kThreads];
for (size_t thread = 0; thread < kThreads; ++thread) {
publishers[thread] = std::thread([&, thread] {
for (size_t index = 0; index < kRecordsPerThread; ++index) {
auto record = u55;
record.delegate_handle = thread * kRecordsPerThread + index;
concurrent_buffer.append(record);
}
});
}
for (auto& publisher : publishers) {
publisher.join();
}
CHECK(concurrent_buffer.size == kThreads * kRecordsPerThread);
CHECK(!concurrent_buffer.overflow);
bool seen[kThreads * kRecordsPerThread] = {};
for (size_t index = 0; index < concurrent_buffer.size; ++index) {
const auto handle = concurrent_records[index].delegate_handle;
CHECK(handle < kThreads * kRecordsPerThread);
CHECK(!seen[handle]);
seen[handle] = true;
}
for (bool found : seen) {
CHECK(found);
}
std::thread overflow_publishers[kThreads];
for (auto& publisher : overflow_publishers) {
publisher = std::thread([&] { concurrent_buffer.append(u85); });
}
for (auto& publisher : overflow_publishers) {
publisher.join();
}
CHECK(concurrent_buffer.size == kThreads * kRecordsPerThread);
CHECK(concurrent_buffer.reset());
std::puts("Ethos-U execution stats: PASS");
}
Loading