Skip to content

Commit 8decea0

Browse files
Make FCB lowering and cleanup safe
Summary: Flexible Context Binary (FCB) compilation already works through `preprocess_multimethod()`, but a single `ExportedProgram` lowered through `to_backend()` (i.e. `QnnBackend.preprocess()`) still took the single-SoC path and failed. This change routes FCB compiler specs from `preprocess()` through `preprocess_multimethod()`; checks `InitContext` results and releases QNN contexts, DLC handles and managers on both success and failure; restores the outer manager registry even when teardown fails; rejects empty or unequal-length target lists; and rejects DLBC combined with reference-weight sharing, which QAIRT does not support. A one-target list still produces a scalar context binary. Tests are added to `TestQNNFcb` in `test_qnn_delegate.py`, which the `test-qnn-delegate-linux` workflow now runs. Authored with Codex; review fixes with Claude Code. Differential Revision: D119582979 Signed-off-by: Ali Monfared <alimonfared@meta.com>
1 parent 254635c commit 8decea0

6 files changed

Lines changed: 448 additions & 39 deletions

File tree

‎.github/workflows/pull.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,9 +1365,9 @@ jobs:
13651365
export QNN_DELEGATE_TEST=1
13661366
export LD_LIBRARY_PATH="$QNN_SDK_ROOT/lib/x86_64-linux-clang/:$(realpath build-x86/lib/):${LD_LIBRARY_PATH:-}"
13671367
1368-
# Run operator test classes only.
1368+
# Run operator and FCB test classes only.
13691369
pytest -vs backends/qualcomm/tests/test_qnn_delegate.py \
1370-
-k "TestQNNFloatingPointOperator or TestQNNQuantizedOperator"
1370+
-k "TestQNNFloatingPointOperator or TestQNNQuantizedOperator or TestQNNFcb"
13711371
13721372
test-phi-3-mini-runner-linux:
13731373
needs: docker-image

‎backends/qualcomm/qnn_preprocess.py‎

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -158,32 +158,50 @@ def preprocess(
158158
) -> PreprocessResult:
159159
option = generate_qnn_executorch_option(compile_specs)
160160
obj_options = flatbuffer_to_option(option)
161+
if obj_options.target_options is not None:
162+
return QnnBackend.preprocess_multimethod(
163+
{DEFAULT_GRAPH_NAME: [edge_program]},
164+
{DEFAULT_GRAPH_NAME: [compile_specs]},
165+
)[DEFAULT_GRAPH_NAME][0]
161166
qnn_manager = get_current_qnn_manager(compile_specs)
162-
qnn_manager.InitContext([DEFAULT_GRAPH_NAME])
163-
py_op_wrapper_list = QnnBackend._build_op_wrappers(
164-
edge_program,
165-
qnn_manager.IsTensorDump(),
166-
obj_options.op_package_options.op_package_infos,
167-
obj_options.use_mha2sha,
168-
obj_options.backend_options.backend_type,
169-
)
167+
init_result = qnn_manager.InitContext([DEFAULT_GRAPH_NAME])
168+
try:
169+
if init_result.value != 0:
170+
raise RuntimeError(
171+
f"Failed to initialize QNN context: {init_result.value}"
172+
)
173+
py_op_wrapper_list = QnnBackend._build_op_wrappers(
174+
edge_program,
175+
qnn_manager.IsTensorDump(),
176+
obj_options.op_package_options.op_package_infos,
177+
obj_options.use_mha2sha,
178+
obj_options.backend_options.backend_type,
179+
)
170180

171-
qnn_context_binary = qnn_manager.Compile(
172-
qnn_manager.GetGraphNames(),
173-
[[py_op_wrapper.GetOpWrapper() for py_op_wrapper in py_op_wrapper_list]],
174-
)
181+
qnn_context_binary = qnn_manager.Compile(
182+
qnn_manager.GetGraphNames(),
183+
[
184+
[
185+
py_op_wrapper.GetOpWrapper()
186+
for py_op_wrapper in py_op_wrapper_list
187+
]
188+
],
189+
)
175190

176-
if obj_options.saver:
177-
exit(
178-
f"Record all QNN API calls from saver backend at: {obj_options.saver_output_dir}"
191+
if obj_options.saver:
192+
exit(
193+
f"Record all QNN API calls from saver backend at: {obj_options.saver_output_dir}"
194+
)
195+
assert (
196+
len(qnn_context_binary) != 0
197+
), "Failed to generate Qnn context binary."
198+
# For now, debug_handle_map is not used by QNN ExecuTorch
199+
return PreprocessResult(
200+
processed_bytes=qnn_context_binary,
201+
debug_handle_map={},
179202
)
180-
assert len(qnn_context_binary) != 0, "Failed to generate Qnn context binary."
181-
qnn_manager.DestroyContext()
182-
# For now, debug_handle_map is not used by QNN ExecuTorch
183-
return PreprocessResult(
184-
processed_bytes=qnn_context_binary,
185-
debug_handle_map={},
186-
)
203+
finally:
204+
qnn_manager.DestroyContext()
187205

188206
@staticmethod
189207
def _populate_delegate_mapping(
@@ -207,8 +225,12 @@ def _populate_delegate_mapping(
207225
@staticmethod
208226
def _get_compile_func(qnn_manager: PyQnnManager.QnnManager):
209227
def compile_func(graph_names, op_wrapper_list):
210-
qnn_manager.InitContext(graph_names)
228+
init_result = qnn_manager.InitContext(graph_names)
211229
try:
230+
if init_result.value != 0:
231+
raise RuntimeError(
232+
f"Failed to initialize QNN context: {init_result.value}"
233+
)
212234
qnn_context_binary = qnn_manager.Compile(graph_names, op_wrapper_list)
213235
finally:
214236
qnn_manager.DestroyContext()
@@ -221,8 +243,13 @@ def _get_compile_func_fcb(qnn_managers: List[PyQnnManager.QnnManager]):
221243
def compile_func(graph_names, op_wrapper_list):
222244
with qnn_managers[0].CreateDlc() as dlc_handle:
223245
for qnn_manager in qnn_managers:
224-
qnn_manager.InitContext(graph_names)
246+
init_result = qnn_manager.InitContext(graph_names)
225247
try:
248+
if init_result.value != 0:
249+
raise RuntimeError(
250+
"Failed to initialize QNN context: "
251+
f"{init_result.value}"
252+
)
226253
qnn_manager.CompileToDlc(
227254
graph_names, op_wrapper_list, dlc_handle
228255
)

0 commit comments

Comments
 (0)