Skip to content
Draft
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
37 changes: 37 additions & 0 deletions backends/qualcomm/tests/test_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,43 @@ def test_custom_op_resolves_supported_types(self):
_resolve_qnn_data_type(py_type, "arg", "my_ops.foo.default")
)

def test_manager_registry_separates_specs_by_precision(self):
"""Two specs for one backend must not share a QnnManager.

Precision comes from the options a manager was built with, so keying the
registry on backend_type alone silently compiled an fp16 partition and a
quantized one at whichever precision was seen first.
"""
from executorch.backends.qualcomm.utils.qnn_manager_lifecycle import (
get_current_qnn_manager,
QnnManagerContext,
)

specs_fp16 = generate_qnn_executorch_compiler_spec(
soc_model=QcomChipset.SM8650,
backend_options=generate_htp_compiler_spec(use_fp16=True),
)
specs_quant = generate_qnn_executorch_compiler_spec(
soc_model=QcomChipset.SM8650,
backend_options=generate_htp_compiler_spec(use_fp16=False),
)
try:
with QnnManagerContext({"fp16": specs_fp16, "quant": specs_quant}):
htp = QnnExecuTorchBackendType.kHtpBackend
self.assertIsNot(
get_current_qnn_manager(htp, specs_fp16),
get_current_qnn_manager(htp, specs_quant),
)
# Same spec must still be reused, not rebuilt.
self.assertIs(
get_current_qnn_manager(htp, specs_fp16),
get_current_qnn_manager(htp, specs_fp16),
)
except RuntimeError as e:
if "QNN" in str(e) or "qnn" in str(e):
self.skipTest(f"QNN SDK not available: {e}")
raise


if __name__ == "__main__":
unittest.main()
32 changes: 20 additions & 12 deletions backends/qualcomm/utils/qnn_manager_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@

class QnnManagerRegistry:
def __init__(self):
# Registry stores {backend_type: QnnManager instance}
# Registry stores {(backend_type, option): QnnManager instance}. Keying on
# the option bytes as well keeps two specs for one backend -- an fp16
# partition and a quantized one, say -- from sharing a manager, since
# precision comes from the options the manager was built with.
self._registry = {}

def get_or_create_qnn_manager(
self, backend_type: QnnExecuTorchBackendType, option: bytes
) -> PyQnnManager.QnnManager:
if backend_type not in self._registry:
key = (backend_type, option)
if key not in self._registry:
qnn_manager = PyQnnManager.QnnManager(option)
err = qnn_manager.InitBackend()
if err.value != 0:
Expand All @@ -35,13 +39,15 @@ def get_or_create_qnn_manager(
"Ensure QNN SDK libraries are available "
"(e.g. LD_LIBRARY_PATH includes $QNN_SDK_ROOT/lib/x86_64-linux-clang/)."
)
self._registry[backend_type] = qnn_manager
return self._registry[backend_type]
self._registry[key] = qnn_manager
return self._registry[key]

def destroy_qnn_manager(self, backend_type: QnnExecuTorchBackendType):
if backend_type in self._registry:
self._registry[backend_type].Destroy()
del self._registry[backend_type]
keys = [k for k in self._registry if k[0] == backend_type]
if keys:
for k in keys:
self._registry[k].Destroy()
del self._registry[k]
else:
logging.warning(
f"Attempted to destroy non-existent QnnManager for backend type {backend_type.name}"
Expand Down Expand Up @@ -83,12 +89,14 @@ def get_current_qnn_manager(
Return a new QnnManger if no QnnManager is active for the given backend_type in the current context.
"""
active_registry = getattr(_current_qnn_managers, "active_registry", None)
if active_registry is None or backend_type not in active_registry._registry:
option = generate_qnn_executorch_option(compile_specs)
if (
active_registry is None
or (backend_type, option) not in active_registry._registry
):
logging.warning(
f"No QnnManager active for backend type {backend_type.name} in the current QnnManagerContext. "
"It would be better to use to_edge_transform_and_lower_to_qnn to lowering to QNN Backend."
)
return QnnManagerRegistry().get_or_create_qnn_manager(
backend_type, generate_qnn_executorch_option(compile_specs)
)
return active_registry._registry[backend_type]
return QnnManagerRegistry().get_or_create_qnn_manager(backend_type, option)
return active_registry._registry[(backend_type, option)]
Loading