From 6b1af15deed4188d2d39e6e4d935978c918fd6ba Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:10:19 +0100 Subject: [PATCH] Fix QNN online-prepare graph handle retrieval Retrieve graphs composed from DLC for ONLINE_PREPARE and exercise the runtime path with a dedicated QNN backend test flow. --- .github/workflows/test-backend-qnn.yml | 4 +-- .../runtime/backends/QnnGraphCommon.cpp | 31 +++++++++++++------ backends/qualcomm/tests/tester.py | 4 +++ backends/test/suite/flow.py | 2 ++ backends/test/suite/flows/qualcomm.py | 3 ++ 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-backend-qnn.yml b/.github/workflows/test-backend-qnn.yml index 939b7c36aee..c42f08482c9 100644 --- a/.github/workflows/test-backend-qnn.yml +++ b/.github/workflows/test-backend-qnn.yml @@ -23,8 +23,8 @@ jobs: backend: qnn flows: >- ${{ github.event_name == 'pull_request' - && '["qnn"]' - || '["qnn", "qnn_16a16w", "qnn_16a8w", "qnn_16a4w", "qnn_16a4w_block", "qnn_8a8w"]' }} + && '["qnn", "qnn_online_prepare"]' + || '["qnn", "qnn_online_prepare", "qnn_16a16w", "qnn_16a8w", "qnn_16a4w", "qnn_16a4w_block", "qnn_8a8w"]' }} ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} timeout: 120 run-linux: true diff --git a/backends/qualcomm/runtime/backends/QnnGraphCommon.cpp b/backends/qualcomm/runtime/backends/QnnGraphCommon.cpp index 8365abaccda..ac74f057663 100644 --- a/backends/qualcomm/runtime/backends/QnnGraphCommon.cpp +++ b/backends/qualcomm/runtime/backends/QnnGraphCommon.cpp @@ -28,9 +28,15 @@ Error QnnGraph::Configure(const std::string& graph_name) { return Error::Ok; } + const auto cache_state = context_->GetCacheState(); Qnn_GraphHandle_t graph_handle = nullptr; - if (context_->GetCacheState() == QnnBackendCache::DESERIALIZE) { - // retrieve QNN Graph + if ( + cache_state == QnnBackendCache::DESERIALIZE || + cache_state == QnnBackendCache::ONLINE_PREPARE) { + // DESERIALIZE restores graphs from a context binary. ONLINE_PREPARE + // registers graphs from DLC in QnnContext::Configure before reaching here. + // In both cases the graph already exists in the context and must be + // retrieved instead of created. error = qnn_interface.qnn_graph_retrieve( context_->GetHandle(), graph_name.c_str(), &graph_handle); if (error != QNN_SUCCESS) { @@ -42,8 +48,8 @@ Error QnnGraph::Configure(const std::string& graph_name) { return Error::Internal; } } else if ( - context_->GetCacheState() == QnnBackendCache::SERIALIZE || - context_->GetCacheState() == QnnBackendCache::MULTI_GRAPH) { + cache_state == QnnBackendCache::SERIALIZE || + cache_state == QnnBackendCache::MULTI_GRAPH) { error = qnn_interface.qnn_graph_create( context_->GetHandle(), graph_name.c_str(), @@ -55,27 +61,32 @@ Error QnnGraph::Configure(const std::string& graph_name) { "qnn_graph_create failed. Error %d", QNN_GET_ERROR_CODE(error)); return Error::Internal; } - } else if (context_->GetCacheState() == QnnBackendCache::ONLINE_PREPARE) { - QNN_EXECUTORCH_LOG_INFO( - "Skip qnn_graph_create, graph has already been composed from Dlc."); } else { QNN_EXECUTORCH_LOG_ERROR("QNN context cache is invalid."); return Error::Internal; } + ET_CHECK_OR_RETURN_ERROR( + graph_handle != nullptr, + Internal, + "QNN graph handle is null after configuring graph %s.", + graph_name.c_str()); + // book keep valid handle of created graph handle_[graph_name] = graph_handle; // The profiler needs to be created after the backend is created. profile_[graph_name] = std::make_unique(implementation_, backend_, profile_level_); - if (context_->GetCacheState() == QnnBackendCache::DESERIALIZE) { + if ( + cache_state == QnnBackendCache::DESERIALIZE || + cache_state == QnnBackendCache::ONLINE_PREPARE) { ET_CHECK_OR_RETURN_ERROR( AfterRetrieveGraph(graph_name) == Error::Ok, Internal, "Fail to configure after retrieving the graph."); } else if ( - context_->GetCacheState() == QnnBackendCache::SERIALIZE || - context_->GetCacheState() == QnnBackendCache::MULTI_GRAPH) { + cache_state == QnnBackendCache::SERIALIZE || + cache_state == QnnBackendCache::MULTI_GRAPH) { ET_CHECK_OR_RETURN_ERROR( AfterCreateGraph(graph_name) == Error::Ok, Internal, diff --git a/backends/qualcomm/tests/tester.py b/backends/qualcomm/tests/tester.py index 86a2eaa92bd..fb4220dcdbf 100644 --- a/backends/qualcomm/tests/tester.py +++ b/backends/qualcomm/tests/tester.py @@ -59,6 +59,7 @@ def __init__( edge_compile_config: Optional[EdgeCompileConfig] = None, soc_model: str = "SM8650", use_fp16: bool = True, + online_prepare: bool = False, ): backend_options = generate_htp_compiler_spec(use_fp16=use_fp16) self.backend_type = backend_options.backend_type @@ -66,6 +67,7 @@ def __init__( self.compiler_specs = generate_qnn_executorch_compiler_spec( soc_model=self.chipset, backend_options=backend_options, + online_prepare=online_prepare, ) super().__init__( @@ -98,9 +100,11 @@ def __init__( example_inputs: Tuple[torch.Tensor], dynamic_shapes: Optional[Tuple[Any]] = None, use_fp16: bool = True, + online_prepare: bool = False, ): def create_to_edge_transform_and_lower(*args, **kwargs): kwargs["use_fp16"] = use_fp16 + kwargs["online_prepare"] = online_prepare return ToEdgeTransformAndLower(*args, **kwargs) # Specialize for Qualcomm diff --git a/backends/test/suite/flow.py b/backends/test/suite/flow.py index 0e5fe2a4ba1..96b2919b469 100644 --- a/backends/test/suite/flow.py +++ b/backends/test/suite/flow.py @@ -143,11 +143,13 @@ def _load_qnn() -> list[TestFlow]: QNN_16A4W_TEST_FLOW, QNN_16A8W_TEST_FLOW, QNN_8A8W_TEST_FLOW, + QNN_ONLINE_PREPARE_TEST_FLOW, QNN_TEST_FLOW, ) return [ QNN_TEST_FLOW, + QNN_ONLINE_PREPARE_TEST_FLOW, QNN_16A16W_TEST_FLOW, QNN_16A8W_TEST_FLOW, QNN_16A4W_TEST_FLOW, diff --git a/backends/test/suite/flows/qualcomm.py b/backends/test/suite/flows/qualcomm.py index 99deb3d4877..0c32fdf5063 100644 --- a/backends/test/suite/flows/qualcomm.py +++ b/backends/test/suite/flows/qualcomm.py @@ -12,12 +12,14 @@ def _create_qnn_flow( per_channel_linear=False, is_qat=False, use_fp16=True, + online_prepare=False, ) -> TestFlow: if quantize and quant_dtype is None: raise RuntimeError("Quant dtype must be provided when quantize is true.") def create_tester(*args, **kwargs) -> QualcommTester: kwargs["use_fp16"] = (use_fp16,) + kwargs["online_prepare"] = online_prepare return QualcommTester(*args, **kwargs) def create_quantize_stage() -> Quantize: @@ -41,6 +43,7 @@ def create_quantize_stage() -> Quantize: QNN_TEST_FLOW = _create_qnn_flow("qnn") +QNN_ONLINE_PREPARE_TEST_FLOW = _create_qnn_flow("qnn_online_prepare", online_prepare=True) QNN_16A16W_TEST_FLOW = _create_qnn_flow( "qnn_16a16w", quantize=True, quant_dtype=QuantDtype.use_16a16w, use_fp16=False )