Skip to content

Commit 8d00ffc

Browse files
Add coreml backend recipes (#13121)
Summary: Adds coreml recipes as discussed similar to xnnpack backend recipes to use them for export flow. Fixes #13100 Pull Request resolved: #13121 Test Plan: `python -m unittest backends/apple/coreml/test/test_coreml_recipes.py` Differential Revision: D79654041 Pulled By: abhinaykukkadapu
1 parent b114f9c commit 8d00ffc

File tree

8 files changed

+474
-18
lines changed

8 files changed

+474
-18
lines changed

backends/apple/coreml/TARGETS

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ runtime.python_library(
6060
],
6161
)
6262

63+
runtime.python_library(
64+
name = "recipes",
65+
srcs = glob([
66+
"recipes/*.py",
67+
]),
68+
visibility = [
69+
"@EXECUTORCH_CLIENTS",
70+
],
71+
deps = [
72+
"fbsource//third-party/pypi/coremltools:coremltools",
73+
":backend",
74+
"//caffe2:torch",
75+
"//executorch/exir:lib",
76+
"//executorch/exir/backend:compile_spec_schema",
77+
"//executorch/exir/backend:partitioner",
78+
"//executorch/exir/backend:utils",
79+
"//executorch/export:lib",
80+
],
81+
)
82+
6383
runtime.cxx_python_extension(
6484
name = "executorchcoreml",
6585
srcs = [
@@ -103,6 +123,7 @@ runtime.python_test(
103123
"fbsource//third-party/pypi/pytest:pytest",
104124
":partitioner",
105125
":quantizer",
126+
":recipes",
106127
"//caffe2:torch",
107128
"//pytorch/vision:torchvision",
108129
],
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Copyright © 2025 Apple Inc. All rights reserved.
2+
#
3+
# Please refer to the license found in the LICENSE file in the root directory of the source tree.
4+
5+
6+
from executorch.export import recipe_registry
7+
8+
from .coreml_recipe_provider import CoreMLRecipeProvider
9+
from .coreml_recipe_types import CoreMLRecipeType
10+
11+
# Auto-register CoreML backend recipe provider
12+
recipe_registry.register_backend_recipe_provider(CoreMLRecipeProvider())
13+
14+
__all__ = [
15+
"CoreMLRecipeProvider",
16+
"CoreMLRecipeType",
17+
]
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Copyright © 2025 Apple Inc. All rights reserved.
2+
#
3+
# Please refer to the license found in the LICENSE file in the root directory of the source tree.
4+
5+
6+
from typing import Any, Optional, Sequence
7+
8+
from executorch.exir import EdgeCompileConfig
9+
10+
try:
11+
import coremltools as ct
12+
except ImportError:
13+
ct = None
14+
15+
from executorch.backends.apple.coreml.compiler import CoreMLBackend
16+
from executorch.backends.apple.coreml.partition.coreml_partitioner import (
17+
CoreMLPartitioner,
18+
)
19+
from executorch.backends.apple.coreml.recipes.coreml_recipe_types import (
20+
COREML_BACKEND,
21+
CoreMLRecipeType,
22+
)
23+
from executorch.export import (
24+
BackendRecipeProvider,
25+
ExportRecipe,
26+
LoweringRecipe,
27+
RecipeType,
28+
)
29+
30+
31+
class CoreMLRecipeProvider(BackendRecipeProvider):
32+
@property
33+
def backend_name(self) -> str:
34+
return COREML_BACKEND
35+
36+
def get_supported_recipes(self) -> Sequence[RecipeType]:
37+
return list(CoreMLRecipeType)
38+
39+
def create_recipe(
40+
self, recipe_type: RecipeType, **kwargs: Any
41+
) -> Optional[ExportRecipe]:
42+
"""Create CoreML recipe with precision and compute unit combinations"""
43+
44+
if recipe_type not in self.get_supported_recipes():
45+
return None
46+
47+
if ct is None:
48+
raise ImportError(
49+
"coremltools is required for CoreML recipes. "
50+
"Install it with: pip install coremltools"
51+
)
52+
53+
# Validate kwargs
54+
self._validate_recipe_kwargs(recipe_type, **kwargs)
55+
56+
# Parse recipe type to get precision and compute unit
57+
precision = None
58+
if recipe_type == CoreMLRecipeType.FP32:
59+
precision = ct.precision.FLOAT32
60+
elif recipe_type == CoreMLRecipeType.FP16:
61+
precision = ct.precision.FLOAT16
62+
63+
if precision is None:
64+
raise ValueError(f"Unknown precision for recipe: {recipe_type.value}")
65+
66+
return self._build_recipe(recipe_type, precision, **kwargs)
67+
68+
def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> None:
69+
if not kwargs:
70+
return
71+
expected_keys = {"minimum_deployment_target", "compute_unit"}
72+
unexpected = set(kwargs.keys()) - expected_keys
73+
if unexpected:
74+
raise ValueError(
75+
f"CoreML Recipes only accept 'minimum_deployment_target' or 'compute_unit' as parameter. "
76+
f"Unexpected parameters: {list(unexpected)}"
77+
)
78+
if "minimum_deployment_target" in kwargs:
79+
minimum_deployment_target = kwargs["minimum_deployment_target"]
80+
if not isinstance(minimum_deployment_target, ct.target):
81+
raise ValueError(
82+
f"Parameter 'minimum_deployment_target' must be an enum of type ct.target, got {type(minimum_deployment_target)}"
83+
)
84+
if "compute_unit" in kwargs:
85+
compute_unit = kwargs["compute_unit"]
86+
if not isinstance(compute_unit, ct.ComputeUnit):
87+
raise ValueError(
88+
f"Parameter 'compute_unit' must be an enum of type ct.ComputeUnit, got {type(compute_unit)}"
89+
)
90+
91+
def _build_recipe(
92+
self,
93+
recipe_type: RecipeType,
94+
precision: ct.precision,
95+
**kwargs: Any,
96+
) -> ExportRecipe:
97+
lowering_recipe = self._get_coreml_lowering_recipe(
98+
compute_precision=precision,
99+
**kwargs,
100+
)
101+
102+
return ExportRecipe(
103+
name=recipe_type.value,
104+
quantization_recipe=None, # TODO - add quantization recipe
105+
lowering_recipe=lowering_recipe,
106+
)
107+
108+
def _get_coreml_lowering_recipe(
109+
self,
110+
compute_precision: ct.precision,
111+
**kwargs: Any,
112+
) -> LoweringRecipe:
113+
compile_specs = CoreMLBackend.generate_compile_specs(
114+
compute_precision=compute_precision,
115+
**kwargs,
116+
)
117+
118+
minimum_deployment_target = kwargs.get("minimum_deployment_target", None)
119+
take_over_mutable_buffer = True
120+
if minimum_deployment_target and minimum_deployment_target < ct.target.iOS18:
121+
take_over_mutable_buffer = False
122+
123+
partitioner = CoreMLPartitioner(
124+
compile_specs=compile_specs,
125+
take_over_mutable_buffer=take_over_mutable_buffer,
126+
)
127+
128+
edge_compile_config = EdgeCompileConfig(
129+
_check_ir_validity=False,
130+
_skip_dim_order=False,
131+
)
132+
133+
return LoweringRecipe(
134+
partitioners=[partitioner], edge_compile_config=edge_compile_config
135+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Copyright © 2025 Apple Inc. All rights reserved.
2+
#
3+
# Please refer to the license found in the LICENSE file in the root directory of the source tree.
4+
5+
6+
from executorch.export import RecipeType
7+
8+
9+
COREML_BACKEND: str = "coreml"
10+
11+
12+
class CoreMLRecipeType(RecipeType):
13+
"""CoreML-specific generic recipe types"""
14+
15+
# FP32 generic recipe, defaults to values published by the CoreML backend and partitioner
16+
# Precision = FP32, Default compute_unit = All (can be overriden by kwargs)
17+
FP32 = "coreml_fp32"
18+
19+
# FP16 generic recipe, defaults to values published by the CoreML backend and partitioner
20+
# Precision = FP32, Default compute_unit = All (can be overriden by kwargs)
21+
FP16 = "coreml_fp16"
22+
23+
@classmethod
24+
def get_backend_name(cls) -> str:
25+
return COREML_BACKEND

0 commit comments

Comments
 (0)