Skip to content

Commit 610219f

Browse files
[Executorch][QNN Recipes] Introduce QNN fp16 recipe
Pull Request resolved: #14126 This diff adds QNN fp16 recipe and tests using htp simulator. Fixes: #13101 ghstack-source-id: 308796609 Differential Revision: [D81945971](https://our.internmc.facebook.com/intern/diff/D81945971/)
1 parent eaa1663 commit 610219f

File tree

5 files changed

+277
-0
lines changed

5 files changed

+277
-0
lines changed

backends/qualcomm/_passes/TARGETS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ runtime.python_library(
1212
],
1313
deps = [
1414
"//executorch/backends/transforms:addmm_mm_to_linear",
15+
"//executorch/backends/transforms:decompose_sdpa",
1516
"//executorch/exir/backend:backend_details",
1617
"//executorch/exir/backend:compile_spec_schema",
1718
],

backends/qualcomm/recipes/TARGETS

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
2+
3+
oncall("executorch")
4+
5+
runtime.python_library(
6+
name = "qnn_recipes",
7+
srcs = [
8+
"__init__.py",
9+
],
10+
visibility = [
11+
"//executorch/...",
12+
"@EXECUTORCH_CLIENTS",
13+
],
14+
deps = [
15+
"//executorch/export:recipe_registry",
16+
":qnn_recipe_provider",
17+
":qnn_recipe_types",
18+
],
19+
)
20+
21+
runtime.python_library(
22+
name = "qnn_recipe_provider",
23+
srcs = [
24+
"qnn_recipe_provider.py",
25+
],
26+
visibility = [
27+
"//executorch/...",
28+
"@EXECUTORCH_CLIENTS",
29+
],
30+
deps = [
31+
"//caffe2:torch",
32+
"//executorch/export:lib",
33+
"//executorch/backends/qualcomm/partition:partition",
34+
"//executorch/backends/qualcomm/serialization:serialization",
35+
"//executorch/backends/qualcomm/utils:utils",
36+
"//executorch/backends/qualcomm/_passes:passes",
37+
":qnn_recipe_types",
38+
],
39+
)
40+
41+
runtime.python_library(
42+
name = "qnn_recipe_types",
43+
srcs = [
44+
"qnn_recipe_types.py",
45+
],
46+
visibility = [
47+
"//executorch/...",
48+
"@EXECUTORCH_CLIENTS",
49+
],
50+
deps = [
51+
"//executorch/export:lib",
52+
],
53+
)

backends/qualcomm/recipes/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""QNN Recipe module for ExecuTorch"""
8+
from executorch.export import recipe_registry
9+
10+
from .qnn_recipe_provider import QNNRecipeProvider
11+
from .qnn_recipe_types import QNNRecipeType
12+
13+
# Auto-register XNNPACK recipe provider
14+
recipe_registry.register_backend_recipe_provider(QNNRecipeProvider())
15+
16+
__all__ = ["QNNRecipeProvider", "QNNRecipeType"]
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# pyre-strict
8+
9+
import logging
10+
from typing import Any, Optional, Sequence
11+
12+
from executorch.backends.qualcomm._passes.qnn_pass_manager import QnnPassManager
13+
from executorch.backends.qualcomm.partition.qnn_partitioner import QnnPartitioner
14+
from executorch.backends.qualcomm.recipes.qnn_recipe_types import (
15+
QNN_BACKEND,
16+
QNNRecipeType,
17+
)
18+
from executorch.backends.qualcomm.serialization.qc_schema import QcomChipset
19+
from executorch.backends.qualcomm.utils.utils import (
20+
generate_htp_compiler_spec,
21+
generate_qnn_executorch_compiler_spec,
22+
get_soc_to_chipset_map,
23+
qnn_edge_config,
24+
)
25+
from executorch.export import (
26+
BackendRecipeProvider,
27+
ExportRecipe,
28+
LoweringRecipe,
29+
RecipeType,
30+
)
31+
32+
33+
class QNNRecipeProvider(BackendRecipeProvider):
34+
@property
35+
def backend_name(self) -> str:
36+
return QNN_BACKEND
37+
38+
def get_supported_recipes(self) -> Sequence[RecipeType]:
39+
return list(QNNRecipeType)
40+
41+
def create_recipe(
42+
self, recipe_type: RecipeType, **kwargs: Any
43+
) -> Optional[ExportRecipe]:
44+
"""Create QNN recipe for different precisions and SoC targets"""
45+
46+
if recipe_type not in self.get_supported_recipes():
47+
return None
48+
49+
self._validate_recipe_kwargs(recipe_type, kwargs)
50+
51+
if recipe_type == QNNRecipeType.FP16:
52+
return self._build_fp16_recipe(recipe_type, kwargs)
53+
54+
return None
55+
56+
def _validate_recipe_kwargs(self, recipe_type: RecipeType, kwargs: Any) -> None:
57+
"""Validate kwargs for each recipe type"""
58+
expected_keys = self._get_expected_keys(recipe_type)
59+
60+
unexpected = set(kwargs.keys()) - expected_keys
61+
if unexpected:
62+
logging.warning(
63+
f"QNN Recipe '{recipe_type.value}' received unexpected parameters: {list(unexpected)}, ignoring them"
64+
)
65+
66+
self._validate_soc_parameter(kwargs)
67+
self._validate_partitioner_parameters(kwargs)
68+
69+
def _get_expected_keys(self, recipe_type: RecipeType) -> set:
70+
"""Get expected parameter keys for a recipe type"""
71+
_ = recipe_type
72+
common_keys = {
73+
"soc_model",
74+
"skip_node_id_set",
75+
"skip_node_op_set",
76+
"skip_mutable_buffer",
77+
}
78+
return common_keys
79+
80+
def _validate_soc_parameter(self, kwargs: Any) -> None:
81+
"""Validate soc_model parameter"""
82+
if "soc_model" in kwargs:
83+
soc_model = kwargs["soc_model"]
84+
if isinstance(soc_model, str):
85+
try:
86+
soc_model = get_soc_to_chipset_map()[soc_model]
87+
kwargs["soc_model"] = soc_model
88+
except KeyError:
89+
raise ValueError(
90+
f"Invalid SoC model '{soc_model}'. Supported models: {[e.name for e in get_soc_to_chipset_map()]}"
91+
)
92+
elif not isinstance(soc_model, QcomChipset):
93+
raise ValueError(
94+
f"Parameter 'soc_model' must be a QcomChipset enum or string, got {type(soc_model)}"
95+
)
96+
else:
97+
raise ValueError("Parameter 'soc_model' is required")
98+
99+
def _validate_partitioner_parameters(self, kwargs: Any) -> None:
100+
"""Validate partitioner parameters"""
101+
if "skip_node_id_set" in kwargs:
102+
skip_node_id_set = kwargs["skip_node_id_set"]
103+
if skip_node_id_set is not None and not isinstance(skip_node_id_set, set):
104+
raise ValueError(
105+
f"Parameter 'skip_node_id_set' must be a set or None, got {type(skip_node_id_set)}"
106+
)
107+
108+
if "skip_node_op_set" in kwargs:
109+
skip_node_op_set = kwargs["skip_node_op_set"]
110+
if skip_node_op_set is not None and not isinstance(skip_node_op_set, set):
111+
raise ValueError(
112+
f"Parameter 'skip_node_op_set' must be a set or None, got {type(skip_node_op_set)}"
113+
)
114+
115+
if "skip_mutable_buffer" in kwargs:
116+
skip_mutable_buffer = kwargs["skip_mutable_buffer"]
117+
if not isinstance(skip_mutable_buffer, bool):
118+
raise ValueError(
119+
f"Parameter 'skip_mutable_buffer' must be a boolean, got {type(skip_mutable_buffer)}"
120+
)
121+
122+
def _build_fp16_recipe(
123+
self,
124+
recipe_type: RecipeType,
125+
kwargs: Any,
126+
) -> ExportRecipe:
127+
soc_model = kwargs["soc_model"]
128+
skip_node_id_set = kwargs.get("skip_node_id_set", None)
129+
skip_node_op_set = kwargs.get("skip_node_op_set", None)
130+
skip_mutable_buffer = kwargs.get("skip_mutable_buffer", False)
131+
132+
lowering_recipe = self._get_qnn_lowering_recipe(
133+
use_fp16=True,
134+
soc_model=soc_model,
135+
skip_node_id_set=skip_node_id_set,
136+
skip_node_op_set=skip_node_op_set,
137+
skip_mutable_buffer=skip_mutable_buffer,
138+
)
139+
140+
return ExportRecipe(
141+
name=recipe_type.value,
142+
aten_transform_passes=[
143+
lambda method_, ep: QnnPassManager().transform_for_export_pipeline(ep)
144+
],
145+
lowering_recipe=lowering_recipe,
146+
)
147+
148+
def _get_qnn_lowering_recipe(
149+
self,
150+
use_fp16: bool,
151+
soc_model: QcomChipset,
152+
skip_node_id_set: Optional[set] = None,
153+
skip_node_op_set: Optional[set] = None,
154+
skip_mutable_buffer: bool = False,
155+
) -> LoweringRecipe:
156+
"""Get QNN lowering recipe with optional precision and SoC target"""
157+
backend_options = generate_htp_compiler_spec(use_fp16=use_fp16)
158+
159+
compile_specs = generate_qnn_executorch_compiler_spec(
160+
soc_model=soc_model,
161+
backend_options=backend_options,
162+
)
163+
164+
partitioner = QnnPartitioner(
165+
compiler_specs=compile_specs,
166+
skip_node_id_set=skip_node_id_set,
167+
skip_node_op_set=skip_node_op_set,
168+
skip_mutable_buffer=skip_mutable_buffer,
169+
)
170+
171+
edge_compile_config = qnn_edge_config()
172+
173+
return LoweringRecipe(
174+
partitioners=[partitioner],
175+
edge_transform_passes=[
176+
lambda method_, ep: QnnPassManager().get_to_edge_transform_passes(ep)
177+
],
178+
edge_compile_config=edge_compile_config,
179+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
# pyre-strict
8+
9+
from executorch.export import RecipeType
10+
11+
12+
QNN_BACKEND: str = "qnn"
13+
14+
15+
class QNNRecipeType(RecipeType):
16+
"""QNN-specific recipe types"""
17+
18+
# FP16 precision recipe, accepts kwargs:
19+
# 1. soc_model
20+
# 2. skip_node_id_set
21+
# 3. skip_node_op_set
22+
# 4. skip_mutable_buffer
23+
24+
FP16 = "qnn_fp16"
25+
26+
@classmethod
27+
def get_backend_name(cls) -> str:
28+
return QNN_BACKEND

0 commit comments

Comments
 (0)