|
| 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 | + ) |
0 commit comments