From d117543721e7be5ec40de34387fa8ed73c416ec8 Mon Sep 17 00:00:00 2001 From: Ali Monfared <2058201+alirezamonfared@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:12:11 -0800 Subject: [PATCH] Add E9945 support and harden ENN integration (#24165) Summary: ## Summary This change improves the Samsung ENN backend integration by: - adding E9945 to `SamsungChipset` and testing compile-spec serialization for every supported chipset; - adding host-side AOT and Android runtime build targets, including static backend registration; - importing `CompileSpec` from the lightweight schema module; - hardening `EnnExecutor` teardown after partial or failed initialization; - fixing portable `size_t` formatting and Android variadic logging. `EnnExecutor` now avoids model and buffer cleanup before successful initialization or model creation, releases buffers only when an allocation exists, and still closes a valid model when no buffers were allocated. Android logging copies the `va_list` and uses `__android_log_vprint` before writing separately to stderr. Existing E9955 and E9965 behavior is unchanged. The backend continues to load ENN dynamically from the device and does not bundle vendor binaries. This is backend infrastructure and contains no application, model, asset, device-eligibility, or rollout policy. ## Authorship Authored with Codex. Test Plan: ## Test plan - Unit tests passed for compile-spec serialization of E9945, E9955, and E9965, including case-insensitive E9945 lookup (2 tests). - Built the Samsung host-side AOT and Android ENN runtime/static-registration targets. - On E9945 hardware, loaded an ExecuTorch program with one ENN delegate and observed successful model open, commit, and execution. - Verified output matched the reference within the expected tolerance and placement logs confirmed NPU participation. - With the ENN runtime library unavailable, verified the backend returned a clean initialization error and the process remained alive. - Lint and format checks passed. Reviewed By: sivapatibandla Differential Revision: D124906831 Pulled By: alirezamonfared --- backends/samsung/BUCK | 64 +++++++++++++++++++ backends/samsung/runtime/BUCK | 52 +++++++++++++++ .../runtime/enn_api_implementation.cpp | 2 + .../samsung/runtime/enn_api_implementation.h | 59 ++++++++--------- backends/samsung/runtime/enn_backend.cpp | 10 ++- backends/samsung/runtime/enn_executor.cpp | 23 +++++-- .../samsung/runtime/enn_executor_allocator.h | 30 +++++++++ backends/samsung/runtime/logging.cpp | 6 +- .../test/enn_executor_allocator_test.cpp | 51 +++++++++++++++ .../samsung/serialization/compile_options.py | 3 +- backends/samsung/test/test_compile_options.py | 38 +++++++++++ 11 files changed, 301 insertions(+), 37 deletions(-) create mode 100644 backends/samsung/BUCK create mode 100644 backends/samsung/runtime/BUCK create mode 100644 backends/samsung/runtime/enn_executor_allocator.h create mode 100644 backends/samsung/runtime/test/enn_executor_allocator_test.cpp create mode 100644 backends/samsung/test/test_compile_options.py diff --git a/backends/samsung/BUCK b/backends/samsung/BUCK new file mode 100644 index 00000000000..d01b9c7d5e0 --- /dev/null +++ b/backends/samsung/BUCK @@ -0,0 +1,64 @@ +load("@fbcode_macros//build_defs:build_file_migration.bzl", "fbcode_target") +load("@fbcode_macros//build_defs:python_unittest.bzl", "python_unittest") +load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") + +oncall("executorch") + +fbcode_target( + _kind = runtime.python_library, + name = "aot_sdk_unbound", + srcs = ["enn_preprocess.py"] + glob([ + "_passes/*.py", + "builders/*.py", + "partition/*.py", + "quantizer/*.py", + "serialization/*.py", + "utils/*.py", + ]), + resources = ["serialization/compile_options_def.fbs"], + labels = ["noautodeps"], + visibility = ["PUBLIC"], + deps = [ + "fbsource//third-party/pypi/numpy:numpy", + "//caffe2:torch", + "//executorch/backends/transforms:addmm_mm_to_linear", + "//executorch/backends/transforms:decompose_sdpa", + "//executorch/backends/transforms:fuse_batch_norm_with_conv", + "//executorch/backends/transforms:lib", + "//executorch/backends/transforms:remove_clone_ops", + "//executorch/backends/transforms:remove_getitem_op", + "//executorch/backends/transforms:utils", + "//executorch/exir:_warnings", + "//executorch/exir:lib", + "//executorch/exir:pass_base", + "//executorch/exir:pass_manager", + "//executorch/exir:sym_util", + "//executorch/exir/_serialize:lib", + "//executorch/exir/backend:backend_details", + "//executorch/exir/backend:compile_spec_schema", + "//executorch/exir/backend:partitioner", + "//executorch/exir/backend:utils", + "//executorch/exir/backend/canonical_partitioners:canonical_partitioner_lib", + "//executorch/exir/dialects:lib", + "//executorch/exir/dialects/edge:lib", + "//executorch/exir/passes:constant_prop_pass", + "//executorch/exir/passes:lib", + "//executorch/exir/program:program", + "//pytorch/ao:torchao", + ], +) + +fbcode_target( + _kind = python_unittest, + name = "compile_options_test", + srcs = [ + "serialization/compile_options.py", + "test/test_compile_options.py", + ], + resources = ["serialization/compile_options_def.fbs"], + deps = [ + "//executorch/exir:_warnings", + "//executorch/exir/_serialize:lib", + "//executorch/exir/backend:compile_spec_schema", + ], +) diff --git a/backends/samsung/runtime/BUCK b/backends/samsung/runtime/BUCK new file mode 100644 index 00000000000..1e9221f73e7 --- /dev/null +++ b/backends/samsung/runtime/BUCK @@ -0,0 +1,52 @@ +# Any targets that should be shared between fbcode and xplat must be defined in +# targets.bzl. This file can contain xplat-only targets. + +load( + "@fbsource//tools/build_defs:default_platform_defs.bzl", + "ANDROID", +) +load("@fbcode_macros//build_defs:build_file_migration.bzl", "non_fbcode_target") +load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") + +oncall("executorch") + +non_fbcode_target( + _kind = runtime.cxx_test, + name = "enn_executor_allocator_test", + srcs = [ + "test/enn_executor_allocator_test.cpp", + ], + headers = glob([ + "*.h", + "*.hpp", + ]), + deps = [ + "//executorch/runtime/core:memory_allocator", + ], +) + +non_fbcode_target( + _kind = runtime.cxx_library, + name = "enn_backend", + srcs = [ + "enn_api_implementation.cpp", + "enn_backend.cpp", + "enn_executor.cpp", + "enn_shared_memory_manager.cpp", + "logging.cpp", + ], + headers = glob([ + "*.h", + "*.hpp", + ]), + modifiers = ["ovr_config//os/version/android/constraints:api-level-26"], + deps = [ + "//executorch/runtime/backend:interface", + "//executorch/runtime/core:memory_allocator", + "//third-party/toolchains:dl", + ], + define_static_target = True, + link_whole = True, + platforms = [ANDROID], + visibility = ["PUBLIC"], +) diff --git a/backends/samsung/runtime/enn_api_implementation.cpp b/backends/samsung/runtime/enn_api_implementation.cpp index 0cdf810d5c4..dd9fcf88abf 100644 --- a/backends/samsung/runtime/enn_api_implementation.cpp +++ b/backends/samsung/runtime/enn_api_implementation.cpp @@ -88,6 +88,8 @@ Error EnnApi::loadApiLib() { ENN_LOAD_API_FUNC(libenn_public_api_, EnnAllocateAllBuffers, this); ENN_LOAD_API_FUNC( libenn_public_api_, EnnAllocateAllBuffersWithSessionId, this); + EnnGenerateBufferSpaceDev = reinterpret_cast( + loadApiFunction(libenn_public_api_, "EnnGenerateBufferSpaceDev", true)); ENN_LOAD_API_FUNC(libenn_public_api_, EnnBufferCommit, this); ENN_LOAD_API_FUNC(libenn_public_api_, EnnGetBuffersInfo, this); ENN_LOAD_API_FUNC(libenn_public_api_, EnnReleaseBuffers, this); diff --git a/backends/samsung/runtime/enn_api_implementation.h b/backends/samsung/runtime/enn_api_implementation.h index 3e0bf1c5f2a..07e4f6e4b3f 100644 --- a/backends/samsung/runtime/enn_api_implementation.h +++ b/backends/samsung/runtime/enn_api_implementation.h @@ -12,8 +12,6 @@ #include #include -#include -#include namespace torch { namespace executor { @@ -60,6 +58,9 @@ class EnnApi { NumberOfBuffersInfo* out_buffers_info, const int session_id, const bool do_commit); + EnnReturn (*EnnGenerateBufferSpaceDev)( + const EnnModelId model_id, + const int session_count); EnnReturn (*EnnExecuteModelWithSessionIdWait)( const EnnModelId model_id, const int session_id); @@ -78,64 +79,64 @@ class EnnApi { *EnnGetFileDescriptorFromEnnBuffer)(EnnBufferPtr buffer, int32_t* fd); private: - static std::mutex instance_mutex_; std::atomic_bool initialize_ = false; // Pointer to the dlopen libs void* libenn_public_api_ = nullptr; - static std::atomic ref_count_; EnnApi(); Error loadApiLib(); Error unloadApiLib(); }; -typedef EnnReturn (*EnnInitialize_fn)(void); -typedef EnnReturn (*EnnSetPreferencePerfMode_fn)(const uint32_t val); -typedef EnnReturn (*EnnGetPreferencePerfMode_fn)(uint32_t* val_ptr); -typedef EnnReturn ( - *EnnOpenModel_fn)(const char* model_file, EnnModelId* model_id); -typedef EnnReturn (*EnnOpenModelFromMemory_fn)( +using EnnInitialize_fn = EnnReturn (*)(void); +using EnnSetPreferencePerfMode_fn = EnnReturn (*)(const uint32_t val); +using EnnGetPreferencePerfMode_fn = EnnReturn (*)(uint32_t* val_ptr); +using EnnOpenModel_fn = + EnnReturn (*)(const char* model_file, EnnModelId* model_id); +using EnnOpenModelFromMemory_fn = EnnReturn (*)( const char* va, const uint32_t size, EnnModelId* model_id); -typedef EnnReturn (*EnnSetFastIpc_fn)(void); -typedef EnnReturn (*EnnUnsetFastIpc_fn)(void); -typedef EnnReturn (*EnnExecuteModelFastIpc_fn)( +using EnnSetFastIpc_fn = EnnReturn (*)(void); +using EnnUnsetFastIpc_fn = EnnReturn (*)(void); +using EnnExecuteModelFastIpc_fn = EnnReturn (*)( const EnnModelId model_id, int client_sleep_usec); -typedef EnnReturn (*EnnExecuteModel_fn)(const EnnModelId model_id); -typedef EnnReturn (*EnnExecuteModelWithSessionIdAsync_fn)( +using EnnExecuteModel_fn = EnnReturn (*)(const EnnModelId model_id); +using EnnExecuteModelWithSessionIdAsync_fn = EnnReturn (*)( const EnnModelId model_id, const int session_id); -typedef EnnReturn (*EnnCloseModel_fn)(const EnnModelId model_id); -typedef EnnReturn (*EnnDeinitialize_fn)(void); -typedef EnnReturn (*EnnAllocateAllBuffers_fn)( +using EnnCloseModel_fn = EnnReturn (*)(const EnnModelId model_id); +using EnnDeinitialize_fn = EnnReturn (*)(void); +using EnnAllocateAllBuffers_fn = EnnReturn (*)( const EnnModelId model_id, EnnBufferPtr** out_buffers, NumberOfBuffersInfo* out_buffers_info); -typedef EnnReturn (*EnnAllocateAllBuffersWithSessionId_fn)( +using EnnAllocateAllBuffersWithSessionId_fn = EnnReturn (*)( const EnnModelId model_id, EnnBufferPtr** out_buffers, NumberOfBuffersInfo* out_buffers_info, const int session_id, const bool do_commit); -typedef EnnReturn (*EnnExecuteModelWithSessionIdWait_fn)( +using EnnGenerateBufferSpaceDev_fn = + EnnReturn (*)(const EnnModelId model_id, const int session_count); +using EnnExecuteModelWithSessionIdWait_fn = EnnReturn (*)( const EnnModelId model_id, const int session_id); -typedef EnnReturn (*EnnBufferCommit_fn)(const EnnModelId model_id); -typedef EnnReturn (*EnnGetBuffersInfo_fn)( +using EnnBufferCommit_fn = EnnReturn (*)(const EnnModelId model_id); +using EnnGetBuffersInfo_fn = EnnReturn (*)( const EnnModelId model_id, NumberOfBuffersInfo* buffers_info); -typedef EnnReturn ( - *EnnReleaseBuffers_fn)(EnnBufferPtr* buffers, const int32_t numOfBuffers); -typedef EnnReturn (*EnnCreateBuffer_fn)( +using EnnReleaseBuffers_fn = + EnnReturn (*)(EnnBufferPtr* buffers, const int32_t numOfBuffers); +using EnnCreateBuffer_fn = EnnReturn (*)( const uint32_t req_size, const uint32_t ion_flag, EnnBufferPtr* out); -typedef EnnReturn (*EnnReleaseBuffer_fn)(EnnBufferPtr buf); -typedef EnnReturn ( - *EnnGetFileDescriptorFromEnnBuffer_fn)(EnnBufferPtr buffer, int32_t* fd); -typedef EnnReturn (*EnnOpenModelFromFd_fn)(int _fd, EnnModelId* model_id); +using EnnReleaseBuffer_fn = EnnReturn (*)(EnnBufferPtr buf); +using EnnGetFileDescriptorFromEnnBuffer_fn = + EnnReturn (*)(EnnBufferPtr buffer, int32_t* fd); +using EnnOpenModelFromFd_fn = EnnReturn (*)(int _fd, EnnModelId* model_id); } // namespace enn } // namespace executor diff --git a/backends/samsung/runtime/enn_backend.cpp b/backends/samsung/runtime/enn_backend.cpp index 81484afc44c..b9e8e431a9f 100644 --- a/backends/samsung/runtime/enn_backend.cpp +++ b/backends/samsung/runtime/enn_backend.cpp @@ -6,7 +6,7 @@ * LICENSE file in the root directory of this source tree. * */ -#include +#include #include #include @@ -37,7 +37,13 @@ class EnnBackend final : public PyTorchBackendInterface { ArrayRef compile_specs) const override { EXYNOS_ATRACE_NAME_LINE("backend_init"); MemoryAllocator* runtime_allocator = context.get_runtime_allocator(); - auto executor = runtime_allocator->allocateInstance(); + if (runtime_allocator == nullptr) { + return Error::MemoryAllocationFailed; + } + auto* executor = enn::allocateEnnExecutor(*runtime_allocator); + if (executor == nullptr) { + return Error::MemoryAllocationFailed; + } const char* binary_buf_addr = reinterpret_cast(processed->data()); size_t buf_size = processed->size(); diff --git a/backends/samsung/runtime/enn_executor.cpp b/backends/samsung/runtime/enn_executor.cpp index 99a2ce11e5e..2f2cf5ae518 100644 --- a/backends/samsung/runtime/enn_executor.cpp +++ b/backends/samsung/runtime/enn_executor.cpp @@ -54,6 +54,17 @@ Error EnnExecutor::initialize(const char* binary_buf_addr, size_t buf_size) { (int)ret); ET_LOG(Info, "Open successfully."); + if (enn_api_inst->EnnGenerateBufferSpaceDev != nullptr) { + ret = enn_api_inst->EnnGenerateBufferSpaceDev(model_id_, 1); + if (ret != ENN_RET_SUCCESS) { + ENN_LOG_WARN( + "EnnGenerateBufferSpaceDev returned %d for model_id = 0x%" PRIX64 + "; continuing with session 0", + (int)ret, + model_id_); + } + } + NumberOfBuffersInfo buffers_info; ret = enn_api_inst->EnnAllocateAllBuffersWithSessionId( model_id_, &alloc_buffer_, &buffers_info, 0, true); @@ -76,13 +87,13 @@ Error EnnExecutor::eval( ET_CHECK_OR_RETURN_ERROR( inputs.size() == getInputSize(), InvalidArgument, - "Invalid number of inputs, expect %" PRIu32 " while get %ld", + "Invalid number of inputs, expect %" PRIu32 " while get %zu", getInputSize(), inputs.size()); ET_CHECK_OR_RETURN_ERROR( outputs.size() == getOutputSize(), InvalidArgument, - "Invalid number of outputs, expected %" PRIu32 " while get %ld", + "Invalid number of outputs, expected %" PRIu32 " while get %zu", getOutputSize(), outputs.size()); @@ -116,9 +127,13 @@ Error EnnExecutor::eval( EnnExecutor::~EnnExecutor() { EXYNOS_ATRACE_FUNCTION_LINE(); const EnnApi* enn_api_inst = EnnApi::getEnnApiInstance(); + if (!enn_api_inst->isInitialized() || model_id_ == 0) { + return; + } NumberOfBuffersInfo buffers_info; - if (enn_api_inst->EnnGetBuffersInfo(model_id_, &buffers_info) == - ENN_RET_SUCCESS) { + if (alloc_buffer_ != nullptr && + enn_api_inst->EnnGetBuffersInfo(model_id_, &buffers_info) == + ENN_RET_SUCCESS) { const int32_t num_of_buffers = buffers_info.n_in_buf + buffers_info.n_out_buf; enn_api_inst->EnnReleaseBuffers(alloc_buffer_, num_of_buffers); diff --git a/backends/samsung/runtime/enn_executor_allocator.h b/backends/samsung/runtime/enn_executor_allocator.h new file mode 100644 index 00000000000..907eb20826d --- /dev/null +++ b/backends/samsung/runtime/enn_executor_allocator.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co. LTD + * 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. + * + */ +#pragma once + +#include +#include + +#include + +namespace torch { +namespace executor { +namespace enn { + +inline EnnExecutor* allocateEnnExecutor(MemoryAllocator& allocator) { + auto* storage = allocator.allocateInstance(); + if (storage == nullptr) { + return nullptr; + } + return new (storage) EnnExecutor(); +} + +} // namespace enn +} // namespace executor +} // namespace torch diff --git a/backends/samsung/runtime/logging.cpp b/backends/samsung/runtime/logging.cpp index 0a2a6e64282..893498009b3 100644 --- a/backends/samsung/runtime/logging.cpp +++ b/backends/samsung/runtime/logging.cpp @@ -17,6 +17,8 @@ void EnnLogImpl(ENN_LOG_LEVEL log_level, const char* format, ...) { va_list args; va_start(args, format); #ifdef __ANDROID__ + va_list android_args; + va_copy(android_args, args); int android_severity = ANDROID_LOG_DEBUG; switch (log_level) { case ENN_LOG_LEVEL::DEBUG: @@ -35,7 +37,9 @@ void EnnLogImpl(ENN_LOG_LEVEL log_level, const char* format, ...) { android_severity = ANDROID_LOG_UNKNOWN; break; } - __android_log_print(android_severity, "[Exynos ExecuTorch]", format, args); + __android_log_vprint( + android_severity, "[Exynos ExecuTorch]", format, android_args); + va_end(android_args); #endif const char* serverity_name; switch (log_level) { diff --git a/backends/samsung/runtime/test/enn_executor_allocator_test.cpp b/backends/samsung/runtime/test/enn_executor_allocator_test.cpp new file mode 100644 index 00000000000..688799741a3 --- /dev/null +++ b/backends/samsung/runtime/test/enn_executor_allocator_test.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Samsung Electronics Co. LTD + * 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 + +#include +#include + +#include + +namespace torch { +namespace executor { +namespace enn { +namespace { + +class FailingMemoryAllocator final : public MemoryAllocator { + public: + FailingMemoryAllocator() : MemoryAllocator(0, nullptr) {} + + void* allocate(size_t, size_t) override { + return nullptr; + } +}; + +TEST(EnnExecutorAllocatorTest, ConstructsExecutorInPoisonedStorage) { + alignas(EnnExecutor) std::array storage; + storage.fill(0x30); + MemoryAllocator allocator(storage.size(), storage.data()); + + EnnExecutor* executor = allocateEnnExecutor(allocator); + + ASSERT_NE(executor, nullptr); + EXPECT_EQ(executor->getInputSize(), 0); + EXPECT_EQ(executor->getOutputSize(), 0); +} + +TEST(EnnExecutorAllocatorTest, ReturnsNullWhenAllocationFails) { + FailingMemoryAllocator allocator; + + EXPECT_EQ(allocateEnnExecutor(allocator), nullptr); +} + +} // namespace +} // namespace enn +} // namespace executor +} // namespace torch diff --git a/backends/samsung/serialization/compile_options.py b/backends/samsung/serialization/compile_options.py index 1b38020927f..47f688812f5 100644 --- a/backends/samsung/serialization/compile_options.py +++ b/backends/samsung/serialization/compile_options.py @@ -17,12 +17,13 @@ from executorch.exir._serialize._dataclass import _DataclassEncoder, _json_to_dataclass from executorch.exir._serialize._flatbuffer import _flatc_compile, _flatc_decompile from executorch.exir._warnings import experimental -from executorch.exir.backend.backend_details import CompileSpec +from executorch.exir.backend.compile_spec_schema import CompileSpec @unique class SamsungChipset(IntEnum): UNDEFINED_CHIP_V = 0 + E9945 = 9945 E9955 = 9955 E9965 = 9965 diff --git a/backends/samsung/test/test_compile_options.py b/backends/samsung/test/test_compile_options.py new file mode 100644 index 00000000000..c7d52f0e809 --- /dev/null +++ b/backends/samsung/test/test_compile_options.py @@ -0,0 +1,38 @@ +# 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. + +import unittest + +from executorch.backends.samsung.serialization.compile_options import ( + ENN_COMPILE_OPTION_TITLE, + gen_samsung_backend_compile_spec, + SamsungChipset, +) + + +class CompileOptionsTest(unittest.TestCase): + def test_supported_chipsets_serialize_to_nonempty_compile_specs(self) -> None: + supported = { + SamsungChipset.E9945, + SamsungChipset.E9955, + SamsungChipset.E9965, + } + self.assertEqual( + supported, + set(SamsungChipset) - {SamsungChipset.UNDEFINED_CHIP_V}, + ) + + for chipset in supported: + with self.subTest(chipset=chipset.name): + compile_spec = gen_samsung_backend_compile_spec(chipset.name) + self.assertEqual(ENN_COMPILE_OPTION_TITLE, compile_spec.key) + self.assertTrue(compile_spec.value) + + def test_chipset_name_is_case_insensitive(self) -> None: + self.assertEqual( + gen_samsung_backend_compile_spec("E9945"), + gen_samsung_backend_compile_spec("e9945"), + )