Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/scripts/test_wheel_package_qnn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ run_core_tests () {
echo "=== [$LABEL] Import smoke tests ==="
"$PYBIN" -c "import executorch; print('executorch imported successfully')"
"$PYBIN" -c "import executorch.backends.qualcomm; print('executorch.backends.qualcomm imported successfully')"
"$PYBIN" -c "from executorch.export.target_recipes import get_android_recipe; recipe = get_android_recipe('android-arm64-snapdragon-fp16'); print(f'executorch.export.target_recipes imported successfully: {recipe}')"

echo "=== [$LABEL] List installed executorch/backends/qualcomm/python ==="
local SITE_DIR
Expand Down
1 change: 1 addition & 0 deletions backends/qualcomm/_passes/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ runtime.python_library(
"//executorch/backends/transforms:decompose_sdpa",
"//executorch/exir/backend:backend_details",
"//executorch/exir/backend:compile_spec_schema",
"//executorch/backends/qualcomm/quantizer:quantizer",
],
)
10 changes: 10 additions & 0 deletions export/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,19 @@ runtime.python_library(
"target_recipes.py",
],
deps = [
":export_utils",
"fbsource//third-party/pypi/coremltools:coremltools",
"//executorch/export:recipe",
"//executorch/backends/xnnpack/recipes:xnnpack_recipes",
"//executorch/backends/apple/coreml:coreml_recipes",
"//executorch/backends/qualcomm/recipes:qnn_recipes",
]
)

runtime.python_library(
name = "export_utils",
srcs = ["utils.py"],
deps = [
"//caffe2:torch",
]
)
120 changes: 94 additions & 26 deletions export/target_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,14 @@
selection and combine multiple backends optimally for target hardware.
"""

import sys
import os
from typing import Dict, List

if sys.platform != "win32":
import coremltools as ct
from executorch.backends.apple.coreml.recipes import CoreMLRecipeType

# pyre-ignore
from executorch.backends.xnnpack.recipes import XNNPackRecipeType
from executorch.export.recipe import ExportRecipe, RecipeType


## IOS Target configs
# The following list of recipes are not exhaustive for CoreML; refer to CoreMLRecipeType for more detailed recipes.
IOS_CONFIGS: Dict[str, List[RecipeType]] = (
{
# pyre-ignore
"ios-arm64-coreml-fp32": [CoreMLRecipeType.FP32, XNNPackRecipeType.FP32],
# pyre-ignore
"ios-arm64-coreml-fp16": [CoreMLRecipeType.FP16],
# pyre-ignore
"ios-arm64-coreml-int8": [CoreMLRecipeType.PT2E_INT8_STATIC],
}
if sys.platform != "win32"
else {}
from executorch.export.utils import (
is_supported_platform_for_coreml_lowering,
is_supported_platform_for_qnn_lowering,
)


Expand All @@ -46,7 +29,7 @@ def _create_target_recipe(
Create a combined recipe for a target.

Args:
target: Human-readable hardware configuration name
target_config: Human-readable hardware configuration name
recipes: List of backend recipe types to combine
**kwargs: Additional parameters - each backend will use what it needs

Expand All @@ -67,7 +50,6 @@ def _create_target_recipe(
f"Failed to create {recipe_type.value} recipe for {target_config}: {e}"
) from e

# Combine into single recipe
if len(backend_recipes) == 1:
return backend_recipes[0]

Expand Down Expand Up @@ -100,8 +82,24 @@ def get_ios_recipe(
recipe = get_ios_recipe('ios-arm64-coreml-int8')
session = export(model, recipe, example_inputs)
"""
if target_config not in IOS_CONFIGS:
supported = list(IOS_CONFIGS.keys())

if not is_supported_platform_for_coreml_lowering():
raise ValueError("CoreML is not supported on this platform")

import coremltools as ct
from executorch.backends.apple.coreml.recipes import CoreMLRecipeType

ios_configs: Dict[str, List[RecipeType]] = {
# pyre-ignore
"ios-arm64-coreml-fp32": [CoreMLRecipeType.FP32, XNNPackRecipeType.FP32],
# pyre-ignore
"ios-arm64-coreml-fp16": [CoreMLRecipeType.FP16],
# pyre-ignore
"ios-arm64-coreml-int8": [CoreMLRecipeType.PT2E_INT8_STATIC],
}

if target_config not in ios_configs:
supported = list(ios_configs.keys())
raise ValueError(
f"Unsupported iOS configuration: '{target_config}'. "
f"Supported: {supported}"
Expand All @@ -113,5 +111,75 @@ def get_ios_recipe(
if "minimum_deployment_target" not in kwargs:
kwargs["minimum_deployment_target"] = ct.target.iOS17

backend_recipes = IOS_CONFIGS[target_config]
backend_recipes = ios_configs[target_config]
return _create_target_recipe(target_config, backend_recipes, **kwargs)


# Android Recipe
def get_android_recipe(
target_config: str = "android-arm64-snapdragon-fp16", **kwargs
) -> ExportRecipe:
"""
Get Android-optimized recipe for specified hardware configuration.

Supported configurations:
- 'android-arm64-snapdragon-fp16': QNN fp16 recipe

Args:
target_config: Android configuration string
**kwargs: Additional parameters for backend recipes

Returns:
ExportRecipe configured for Android deployment

Raises:
ValueError: If target configuration is not supported

Example:
recipe = get_android_recipe('android-arm64-snapdragon-fp16')
session = export(model, recipe, example_inputs)
"""

if not is_supported_platform_for_qnn_lowering():
raise ValueError(
"QNN is not supported or not properly configured on this platform"
)

try:
# Qualcomm QNN backend runs QNN sdk download on first use
# with a pip install, so wrap it in a try/except
# pyre-ignore
from executorch.backends.qualcomm.recipes import QNNRecipeType

# (1) if this is called from a pip install, the QNN SDK will be available
# (2) if this is called from a source build, check if qnn is available otherwise, had to run build.sh
if os.getenv("QNN_SDK_ROOT", None) is None:
raise ValueError(
"QNN SDK not found, cannot use QNN recipes. First run `./backends/qualcomm/scripts/build.sh`, if building from source"
)
except Exception as e:
raise ValueError(
"QNN backend is not available. Please ensure the Qualcomm backend "
"is properly installed and configured, "
) from e

android_configs: Dict[str, List[RecipeType]] = {
# pyre-ignore
"android-arm64-snapdragon-fp16": [QNNRecipeType.FP16],
}

if target_config not in android_configs:
supported = list(android_configs.keys())
raise ValueError(
f"Unsupported Android configuration: '{target_config}'. "
f"Supported: {supported}"
)

kwargs = kwargs or {}

if target_config == "android-arm64-snapdragon-fp16":
if "soc_model" not in kwargs:
kwargs["soc_model"] = "SM8650"

backend_recipes = android_configs[target_config]
return _create_target_recipe(target_config, backend_recipes, **kwargs)
13 changes: 13 additions & 0 deletions export/tests/TARGETS
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
load("@fbsource//xplat/executorch/backends/qualcomm/qnn_version.bzl", "get_qnn_library_version")

oncall("executorch")

Expand Down Expand Up @@ -37,11 +38,23 @@ runtime.python_test(
srcs = [
"test_target_recipes.py",
],
env = {
"LD_LIBRARY_PATH": "$(location fbsource//third-party/qualcomm/qnn/qnn-{0}:qnn_offline_compile_libs)".format(get_qnn_library_version()),
"QNN_SDK_ROOT": "$(location fbsource//third-party/qualcomm/qnn/qnn-{0}:__dir__)".format(get_qnn_library_version()),
"HTTP_PROXY": "http://fwdproxy:8080",
"HTTPS_PROXY": "http://fwdproxy:8080",
},
labels = ["long_running"],
deps = [
"//executorch/export:lib",
"//executorch/export:target_recipes",
"//executorch/export:export_utils",
"//executorch/runtime:runtime",
"//executorch/backends/xnnpack/recipes:xnnpack_recipes",
"//executorch/backends/apple/coreml:coreml_recipes",
"//executorch/backends/qualcomm/recipes:qnn_recipes",
"//executorch/examples/models:models",
"//executorch/backends/xnnpack/test/tester:tester",
"fbsource//third-party/pypi/coremltools:coremltools"
]
)
Loading
Loading