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"), + )