Skip to content

Commit b3fde42

Browse files
JakeStevensfacebook-github-bot
authored andcommitted
Allow context-binary lowering to use edge dialect ops (#20518)
Summary: Unblocks the QNN context-binary path from lowering through `to_edge` with `_use_edge_ops=True` (the default). Previously it was pinned to `EdgeCompileConfig(_use_edge_ops=False)` purely to keep the `qaisw` context-loader custom op's original name, because loader detection was name-based. We can simply unwrap the op overload and maintain the same check. Differential Revision: D109598309
1 parent 7013c8d commit b3fde42

3 files changed

Lines changed: 98 additions & 6 deletions

File tree

backends/qualcomm/qnn_preprocess.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
)
3737
from executorch.exir.backend.utils import DelegateMappingBuilder
3838
from executorch.exir.debug_handle_utils import DEBUG_HANDLE_KEY
39+
from executorch.exir.operator.convert import unwrap_op_overload
3940
from torch.export.exported_program import ExportedProgram
4041

4142
DEFAULT_DEBUG_HANDLE = 65535
@@ -90,11 +91,12 @@ def _build_op_wrappers(
9091
"is not supported in Qnn Delegate"
9192
)
9293
try:
94+
op = unwrap_op_overload(node.target)
9395
context_loader_target = eval(
94-
f"torch.ops.{OpContextLoader.namespace}.{node.target.__name__}",
96+
f"torch.ops.{OpContextLoader.namespace}.{op.__name__}",
9597
globals().update(torch.__dict__),
9698
)
97-
assert node.target == context_loader_target, err_msg
99+
assert op == context_loader_target, err_msg
98100
# if graph has context binary loader node, return directly
99101
return node.meta[OpContextLoader.meta_ctx_bin]
100102
except:

backends/qualcomm/tests/test_passes.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
from unittest.mock import MagicMock
23

34
import torch
45
from executorch.backends.qualcomm._passes import (
@@ -13,6 +14,9 @@
1314
from executorch.backends.qualcomm._passes.qnn_pass_manager import (
1415
get_qnn_pass_manager_cls,
1516
)
17+
from executorch.backends.qualcomm.builders.qnn_constants import OpContextLoader
18+
from executorch.backends.qualcomm.partition.qnn_partitioner import QnnOperatorSupport
19+
from executorch.backends.qualcomm.qnn_preprocess import QnnBackend
1620
from executorch.backends.qualcomm.quantizer.quantizer import QnnQuantizer, QuantDtype
1721
from executorch.backends.qualcomm.serialization.qc_schema import (
1822
QcomChipset,
@@ -28,13 +32,101 @@
2832
generate_qnn_executorch_compiler_spec,
2933
to_edge_transform_and_lower_to_qnn,
3034
)
31-
from executorch.exir import to_edge
35+
from executorch.exir import EdgeCompileConfig, to_edge
3236
from executorch.exir.debug_handle_utils import DEBUG_HANDLE_KEY
3337
from executorch.exir.dialects._ops import ops as exir_ops
38+
from torch.library import Library
3439
from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e
3540

3641

3742
class TestPasses(unittest.TestCase):
43+
def _build_context_loader_edge_program(self, op_name, check_ir_validity=True):
44+
graph_name = "forward"
45+
custom_op = Library(OpContextLoader.namespace, "FRAGMENT")
46+
self.addCleanup(custom_op._destroy)
47+
custom_op.define(f"{op_name}(Tensor[] inputs) -> Any")
48+
49+
@torch.library.impl(
50+
custom_op, op_name, dispatch_key="CompositeExplicitAutograd"
51+
)
52+
def op_impl(inputs):
53+
return (torch.zeros((1, 2), device="meta", dtype=inputs[0].dtype),)
54+
55+
class Model(torch.nn.Module):
56+
def forward(self, x):
57+
return getattr(
58+
getattr(torch.ops, OpContextLoader.namespace), op_name
59+
).default((x,))
60+
61+
exported_program = torch.export.export(
62+
Model(), (torch.ones(1, 2),), strict=True
63+
)
64+
compile_config = (
65+
None if check_ir_validity else EdgeCompileConfig(_check_ir_validity=False)
66+
)
67+
edge_program_manager = to_edge(
68+
{graph_name: exported_program},
69+
compile_config=compile_config,
70+
)
71+
edge_program = edge_program_manager._edge_programs[graph_name]
72+
context_loader_nodes = [
73+
node
74+
for node in edge_program.graph.nodes
75+
if node.op == "call_function"
76+
and OpContextLoader.namespace in str(node.target)
77+
]
78+
return edge_program, context_loader_nodes
79+
80+
def test_context_loader_edge_op_is_delegated(self):
81+
op_name = "ctx_loader_delegation"
82+
ctx_bin = b"qnn_context_binary"
83+
_, context_loader_nodes = self._build_context_loader_edge_program(
84+
op_name, check_ir_validity=False
85+
)
86+
self.assertEqual(1, len(context_loader_nodes))
87+
context_loader_nodes[0].meta[OpContextLoader.meta_ctx_bin] = ctx_bin
88+
89+
# A fully constructed QnnOperatorSupport needs a live QNN manager, so
90+
# drive is_node_supported with a mock self: the context-loader node is
91+
# force-passed without touching instance state beyond the log label.
92+
support = MagicMock()
93+
support.phase = "QnnPartitioner"
94+
self.assertTrue(
95+
QnnOperatorSupport.is_node_supported(support, None, context_loader_nodes[0])
96+
)
97+
98+
def test_build_op_wrappers_returns_context_binary(self):
99+
op_name = "ctx_loader_build"
100+
ctx_bin = b"qnn_context_binary"
101+
edge_program, context_loader_nodes = self._build_context_loader_edge_program(
102+
op_name, check_ir_validity=False
103+
)
104+
for node in context_loader_nodes:
105+
node.meta[OpContextLoader.meta_ctx_bin] = ctx_bin
106+
107+
# For a graph whose only op is the context-binary loader, _build_op_wrappers
108+
# returns the stamped context binary directly, before any QNN compilation.
109+
result = QnnBackend._build_op_wrappers(
110+
edge_program,
111+
enable_tensor_dump=False,
112+
op_package_infos=[],
113+
use_mha2sha=False,
114+
backend_type=QnnExecuTorchBackendType.kHtpBackend,
115+
)
116+
self.assertEqual(ctx_bin, result)
117+
118+
def test_context_loader_op_lowers_with_ir_validation(self):
119+
op_name = "ctx_loader_validation"
120+
121+
# from_context_binary lowers with IR validity checks enabled (the
122+
# default). The context-loader custom op survives the edge verifier
123+
# because its namespace is not aten, so no validation is disabled and
124+
# the loader node is still present for downstream stamping.
125+
_, context_loader_nodes = self._build_context_loader_edge_program(
126+
op_name, check_ir_validity=True
127+
)
128+
self.assertEqual(1, len(context_loader_nodes))
129+
38130
def _build_quantized_graph(self):
39131
"""Build a quantized graph through AnnotateQuantAttrs + FoldQDQ."""
40132

backends/qualcomm/utils/utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
)
6666
from executorch.backends.qualcomm.utils.qnn_manager_lifecycle import QnnManagerContext
6767

68-
from executorch.exir import EdgeCompileConfig, ExirExportedProgram, to_edge
68+
from executorch.exir import ExirExportedProgram, to_edge
6969
from executorch.exir.backend.compile_spec_schema import CompileSpec
7070
from executorch.exir.lowered_backend_module import LoweredBackendModule
7171
from executorch.exir.program._program import (
@@ -947,8 +947,6 @@ def preprocess_binary(ctx_bin, compiler_specs):
947947
# temporarily remove the first parameter name.
948948
edge_prog_mgr = to_edge(
949949
{graph_name: bundle_prog["exported_program"]},
950-
# do not alter name for custom op
951-
compile_config=EdgeCompileConfig(_use_edge_ops=False),
952950
)
953951

954952
# update meta with context binary

0 commit comments

Comments
 (0)