blob: d9e90abb1d3d94a6705053cd665783bbdd6af7bf [file] [log] [blame] [edit]
/*
* 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.
*/
#include <executorch/backends/xnnpack/runtime/XNNCompiler.h>
#include <executorch/runtime/backend/interface.h>
#include <executorch/runtime/core/error.h>
#include <executorch/runtime/core/evalue.h>
#include <executorch/runtime/platform/profiler.h>
#include <memory>
#include <mutex>
#pragma clang diagnostic ignored "-Wglobal-constructors"
namespace executorch {
namespace backends {
using executorch::runtime::ArrayRef;
using executorch::runtime::Backend;
using executorch::runtime::BackendExecutionContext;
using executorch::runtime::BackendInitContext;
using executorch::runtime::CompileSpec;
using executorch::runtime::DelegateHandle;
using executorch::runtime::Error;
using executorch::runtime::EValue;
using executorch::runtime::FreeableBuffer;
using executorch::runtime::Result;
class XnnpackBackend final : public ::executorch::runtime::BackendInterface {
public:
~XnnpackBackend() = default;
XnnpackBackend() {
// Initialize XNNPACK
xnn_status status = xnn_initialize(/*allocator=*/nullptr);
if (status != xnn_status_success) {
ET_LOG(
Error,
"Failed to initialize, XNNPACK status: 0x%x",
(unsigned int)status);
return;
}
#ifdef ENABLE_XNNPACK_SHARED_WORKSPACE
// Create a workspace for the XNNExecutor to use. This workspace will be
// shared across all delegate instances.
ET_LOG(Debug, "Creating XNN workspace");
xnn_workspace_t workspace = nullptr;
status = xnn_create_workspace(&workspace);
if (status != xnn_status_success) {
ET_LOG(
Error,
"Failed to create XNN workspace, XNNPACK status: 0x%x",
(unsigned int)status);
workspace = nullptr;
return;
}
workspace_.reset(workspace);
ET_LOG(Debug, "Created XNN workspace: %p", workspace_.get());
#endif // ENABLE_XNNPACK_SHARED_WORKSPACE
}
bool is_available() const override {
return xnn_status_success == xnn_initialize(/*allocator=*/nullptr);
}
Result<DelegateHandle*> init(
BackendInitContext& context,
FreeableBuffer* processed,
ArrayRef<CompileSpec> compile_specs) const override {
auto executor = ET_ALLOCATE_INSTANCE_OR_RETURN_ERROR(
context.get_runtime_allocator(), xnnpack::delegate::XNNExecutor);
// Executor has been allocated but not constructed, ensure that runtime_ is
// nullptr by constructing it in place here. NOTE: Since we use placement
// new and since this type is not trivially destructible, we must call the
// destructor manually in destroy().
new (executor) xnnpack::delegate::XNNExecutor;
Error err = xnnpack::delegate::XNNCompiler::compileModel(
processed->data(),
processed->size(),
executor,
context.get_runtime_allocator(),
workspace_.get());
// This backend does not need its processed data after compiling the model.
processed->Free();
if (err != Error::Ok) {
// destroy() won't be called on this handle, so we need to clean it up
// now.
executor->~XNNExecutor();
ET_LOG(
Error, "XNNCompiler::compileModel failed: 0x%x", (unsigned int)err);
return err;
}
return executor;
}
Error execute(
BackendExecutionContext& context,
DelegateHandle* handle,
EValue** args) const override {
auto executor = static_cast<xnnpack::delegate::XNNExecutor*>(handle);
#ifdef ENABLE_XNNPACK_SHARED_WORKSPACE
const std::lock_guard<std::mutex> lock(workspace_mutex_);
#endif
// Prepare Inputs/Outputs and Propagate Input Shapes
Error err = executor->prepare_args(args);
if (err != Error::Ok) {
return err;
}
err = executor->forward(context);
if (err != Error::Ok) {
return err;
}
// Resize outputs and recast pointers if necessary
err = executor->resize_outputs(args);
return err;
}
void destroy(DelegateHandle* handle) const override {
if (handle != nullptr) {
#ifdef ENABLE_XNNPACK_SHARED_WORKSPACE
// This is needed to serialize access to xnn_delete_runtime which is not
// thread safe. This can heppen when multiple threads call destroy() on
// the same backend instance.
const std::lock_guard<std::mutex> lock(workspace_mutex_);
#endif
auto executor = static_cast<xnnpack::delegate::XNNExecutor*>(handle);
#ifdef ENABLE_XNNPACK_PROFILING
executor->print_avg_op_timings();
#endif
// XNNExecutor is not trivially destructible. Since this was constructed
// manually in init(), we must destroy it manually here.
executor->~XNNExecutor();
}
}
private:
// This is a global workspace for all delegate instances.
mutable std::mutex workspace_mutex_;
std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> workspace_{
nullptr,
&xnn_release_workspace};
};
namespace {
auto cls = XnnpackBackend();
Backend backend{"XnnpackBackend", &cls};
static auto success_with_compiler = register_backend(backend);
} // namespace
} // namespace backends
} // namespace executorch