Skip to content

Commit 362a88f

Browse files
alirezamonfaredmeta-codesync[bot]
authored andcommitted
Isolate cached delegates by Module lifetime (pytorch#24462)
Summary: Pull Request resolved: pytorch#24462 ## Summary The QNN backend caches a `QnnManager` so methods in one ExecuTorch `Module` can share an expensive QNN context. The previous cache key used only the exported context signature, so independent modules loading equivalent programs could share a manager that was placement-allocated from one module's runtime allocator. Key the cache by both context signature and runtime allocator. Methods in one module continue to share a context, while independent modules receive managers with independent lifetimes. The change also: - serializes cache lookup, insertion, membership checks, and removal; - avoids allocating a manager on cache hits; - removes cache state before running the non-trivial `QnnManager` destructor. ## Scope This fixes sequential independent-module lifetime isolation. It does not change exported program contents, backend selection, or model behavior. Broader same-module concurrent execution and initialization races remain outside this change. Differential Revision: D122851825
1 parent 893d815 commit 362a88f

2 files changed

Lines changed: 51 additions & 30 deletions

File tree

‎backends/qualcomm/runtime/QnnExecuTorchBackend.cpp‎

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,30 @@ Result<DelegateHandle*> QnnExecuTorchBackend::init(
6565
QNN_EXECUTORCH_LOG_WARN("unknown argument: %s", compile_spec.key);
6666
}
6767

68-
// Create QnnManager
6968
MemoryAllocator* runtime_allocator = context.get_runtime_allocator();
70-
QnnManager* qnn_manager = runtime_allocator->allocateInstance<QnnManager>();
71-
if (qnn_manager == nullptr) {
72-
return Error::MemoryAllocationFailed;
73-
}
74-
75-
// NOTE: Since we use placement new and since this type is not trivially
76-
// destructible, we must call the destructor manually in destroy().
77-
new (qnn_manager) QnnManager(qnn_executorch_options, qnn_context_blob);
7869
// TODO: this is a temporal solution for multi-graph support, will be
7970
// removed once framework starts to accept runtime configuration
8071
// ---
8172
// check if current context binary has already been initialized
8273
// return cached one for reducing memory footprint
83-
84-
auto iter = delegate_map_.find(signature);
85-
if (iter != delegate_map_.end()) {
74+
const DelegateKey key{signature, runtime_allocator};
75+
if (DelegateHandle* cached_handle = get_cached_delegate(key)) {
8676
QNN_EXECUTORCH_LOG_INFO(
8777
"Use cached delegate handle for current method: %s",
8878
context.get_method_name());
89-
return iter->second;
79+
return cached_handle;
80+
}
81+
82+
// Create QnnManager
83+
QnnManager* qnn_manager = runtime_allocator->allocateInstance<QnnManager>();
84+
if (qnn_manager == nullptr) {
85+
return Error::MemoryAllocationFailed;
9086
}
9187

88+
// NOTE: Since we use placement new and since this type is not trivially
89+
// destructible, we must call the destructor manually in destroy().
90+
new (qnn_manager) QnnManager(qnn_executorch_options, qnn_context_blob);
91+
9292
ET_CHECK_OR_RETURN_ERROR(
9393
qnn_manager->InitBackend() == Error::Ok,
9494
Internal,
@@ -111,7 +111,7 @@ Result<DelegateHandle*> QnnExecuTorchBackend::init(
111111
"Fail to allocate tensor");
112112
}
113113
}
114-
add_cached_delegate(signature, qnn_manager);
114+
add_cached_delegate(key, qnn_manager);
115115

116116
#ifndef __hexagon__
117117
// This backend does not need its processed data after Init.
@@ -126,9 +126,7 @@ Error QnnExecuTorchBackend::execute(
126126
DelegateHandle* handle,
127127
Span<EValue*> args) const {
128128
ET_CHECK_OR_RETURN_ERROR(
129-
delegate_map_rev_.count(handle) != 0,
130-
Internal,
131-
"DelegateHandle has been deleted");
129+
is_cached_delegate(handle), Internal, "DelegateHandle has been deleted");
132130
QnnManager* qnn_manager = static_cast<QnnManager*>(handle);
133131

134132
std::string method_name = context.get_method_name();
@@ -225,10 +223,8 @@ Error QnnExecuTorchBackend::execute(
225223
}
226224

227225
void QnnExecuTorchBackend::destroy(DelegateHandle* handle) const {
228-
if (handle != nullptr && delegate_map_rev_.count(handle)) {
229-
QnnManager* qnn_manager = static_cast<QnnManager*>(handle);
230-
qnn_manager->Destroy();
231-
erase_cached_delegate(handle);
226+
if (handle != nullptr && erase_cached_delegate(handle)) {
227+
static_cast<QnnManager*>(handle)->~QnnManager();
232228
}
233229
}
234230

@@ -365,23 +361,37 @@ bool QnnExecuTorchBackend::is_available() const {
365361
return true;
366362
}
367363

364+
executorch::runtime::DelegateHandle* QnnExecuTorchBackend::get_cached_delegate(
365+
const DelegateKey& key) const {
366+
std::lock_guard<std::mutex> guard(mutex_);
367+
auto iter = delegate_map_.find(key);
368+
return iter == delegate_map_.end() ? nullptr : iter->second;
369+
}
370+
368371
void QnnExecuTorchBackend::add_cached_delegate(
369-
const std::int64_t& signature,
372+
const DelegateKey& key,
370373
executorch::runtime::DelegateHandle* handle) const {
371374
std::lock_guard<std::mutex> guard(mutex_);
372-
delegate_map_[signature] = handle;
373-
delegate_map_rev_[handle] = signature;
375+
delegate_map_[key] = handle;
376+
delegate_map_rev_[handle] = key;
374377
}
375378

376-
void QnnExecuTorchBackend::erase_cached_delegate(
379+
bool QnnExecuTorchBackend::is_cached_delegate(
380+
executorch::runtime::DelegateHandle* handle) const {
381+
std::lock_guard<std::mutex> guard(mutex_);
382+
return delegate_map_rev_.count(handle) != 0;
383+
}
384+
385+
bool QnnExecuTorchBackend::erase_cached_delegate(
377386
executorch::runtime::DelegateHandle* handle) const {
378387
std::lock_guard<std::mutex> guard(mutex_);
379388
auto iter = delegate_map_rev_.find(handle);
380389
if (iter == delegate_map_rev_.end()) {
381-
return;
390+
return false;
382391
}
383392
delegate_map_.erase(iter->second);
384393
delegate_map_rev_.erase(handle);
394+
return true;
385395
}
386396

387397
namespace {

‎backends/qualcomm/runtime/QnnExecuTorchBackend.h‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
#include <executorch/runtime/core/error.h>
1313
#include <executorch/runtime/core/evalue.h>
1414

15+
#include <map>
1516
#include <mutex>
1617
#include <unordered_map>
18+
#include <utility>
1719

1820
namespace executorch {
1921
namespace backends {
@@ -51,16 +53,25 @@ class QnnExecuTorchBackend final
5153
bool is_available() const override;
5254

5355
private:
56+
// A context binary signature plus the runtime allocator that the methods of
57+
// one Module share. Other Modules can be destroyed independently, so they
58+
// must never receive this Module's QnnManager.
59+
using DelegateKey =
60+
std::pair<std::int64_t, const executorch::runtime::MemoryAllocator*>;
61+
62+
executorch::runtime::DelegateHandle* get_cached_delegate(
63+
const DelegateKey& key) const;
5464
void add_cached_delegate(
55-
const std::int64_t& signature,
65+
const DelegateKey& key,
5666
executorch::runtime::DelegateHandle* handle) const;
57-
void erase_cached_delegate(executorch::runtime::DelegateHandle* handle) const;
67+
bool is_cached_delegate(executorch::runtime::DelegateHandle* handle) const;
68+
bool erase_cached_delegate(executorch::runtime::DelegateHandle* handle) const;
5869

5970
mutable std::mutex mutex_;
6071
mutable std::mutex runtime_option_mutex_;
61-
mutable std::unordered_map<int64_t, executorch::runtime::DelegateHandle*>
72+
mutable std::map<DelegateKey, executorch::runtime::DelegateHandle*>
6273
delegate_map_;
63-
mutable std::unordered_map<executorch::runtime::DelegateHandle*, std::int64_t>
74+
mutable std::unordered_map<executorch::runtime::DelegateHandle*, DelegateKey>
6475
delegate_map_rev_;
6576

6677
RuntimeOption qnn_runtime_log_level_{false, 0};

0 commit comments

Comments
 (0)