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
21 changes: 21 additions & 0 deletions backends/apple/coreml/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ runtime.python_library(
],
)

runtime.python_library(
name = "recipes",
srcs = glob([
"recipes/*.py",
]),
visibility = [
"@EXECUTORCH_CLIENTS",
],
deps = [
"fbsource//third-party/pypi/coremltools:coremltools",
":backend",
"//caffe2:torch",
"//executorch/exir:lib",
"//executorch/exir/backend:compile_spec_schema",
"//executorch/exir/backend:partitioner",
"//executorch/exir/backend:utils",
"//executorch/export:lib",
],
)

runtime.cxx_python_extension(
name = "executorchcoreml",
srcs = [
Expand Down Expand Up @@ -103,6 +123,7 @@ runtime.python_test(
"fbsource//third-party/pypi/pytest:pytest",
":partitioner",
":quantizer",
":recipes",
"//caffe2:torch",
"//pytorch/vision:torchvision",
],
Expand Down
17 changes: 17 additions & 0 deletions backends/apple/coreml/recipes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright © 2025 Apple Inc. All rights reserved.
#
# Please refer to the license found in the LICENSE file in the root directory of the source tree.


from executorch.export import recipe_registry

from .coreml_recipe_provider import CoreMLRecipeProvider
from .coreml_recipe_types import CoreMLRecipeType

# Auto-register CoreML backend recipe provider
recipe_registry.register_backend_recipe_provider(CoreMLRecipeProvider())

__all__ = [
"CoreMLRecipeProvider",
"CoreMLRecipeType",
]
132 changes: 132 additions & 0 deletions backends/apple/coreml/recipes/coreml_recipe_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Copyright © 2025 Apple Inc. All rights reserved.
#
# Please refer to the license found in the LICENSE file in the root directory of the source tree.


from typing import Any, Optional, Sequence

import coremltools as ct

from executorch.backends.apple.coreml.compiler import CoreMLBackend
from executorch.backends.apple.coreml.partition.coreml_partitioner import (
CoreMLPartitioner,
)
from executorch.backends.apple.coreml.recipes.coreml_recipe_types import (
COREML_BACKEND,
CoreMLRecipeType,
)

from executorch.exir import EdgeCompileConfig
from executorch.export import (
BackendRecipeProvider,
ExportRecipe,
LoweringRecipe,
RecipeType,
)


class CoreMLRecipeProvider(BackendRecipeProvider):
@property
def backend_name(self) -> str:
return COREML_BACKEND

def get_supported_recipes(self) -> Sequence[RecipeType]:
return list(CoreMLRecipeType)

def create_recipe(
self, recipe_type: RecipeType, **kwargs: Any
) -> Optional[ExportRecipe]:
"""Create CoreML recipe with precision and compute unit combinations"""

if recipe_type not in self.get_supported_recipes():
return None

if ct is None:
raise ImportError(
"coremltools is required for CoreML recipes. "
"Install it with: pip install coremltools"
)

# Validate kwargs
self._validate_recipe_kwargs(recipe_type, **kwargs)

# Parse recipe type to get precision and compute unit
precision = None
if recipe_type == CoreMLRecipeType.FP32:
precision = ct.precision.FLOAT32
elif recipe_type == CoreMLRecipeType.FP16:
precision = ct.precision.FLOAT16

if precision is None:
raise ValueError(f"Unknown precision for recipe: {recipe_type.value}")

return self._build_recipe(recipe_type, precision, **kwargs)

def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> None:
if not kwargs:
return
expected_keys = {"minimum_deployment_target", "compute_unit"}
unexpected = set(kwargs.keys()) - expected_keys
if unexpected:
raise ValueError(
f"CoreML Recipes only accept 'minimum_deployment_target' or 'compute_unit' as parameter. "
f"Unexpected parameters: {list(unexpected)}"
)
if "minimum_deployment_target" in kwargs:
minimum_deployment_target = kwargs["minimum_deployment_target"]
if not isinstance(minimum_deployment_target, ct.target):
raise ValueError(
f"Parameter 'minimum_deployment_target' must be an enum of type ct.target, got {type(minimum_deployment_target)}"
)
if "compute_unit" in kwargs:
compute_unit = kwargs["compute_unit"]
if not isinstance(compute_unit, ct.ComputeUnit):
raise ValueError(
f"Parameter 'compute_unit' must be an enum of type ct.ComputeUnit, got {type(compute_unit)}"
)

def _build_recipe(
self,
recipe_type: RecipeType,
precision: ct.precision,
**kwargs: Any,
) -> ExportRecipe:
lowering_recipe = self._get_coreml_lowering_recipe(
compute_precision=precision,
**kwargs,
)

return ExportRecipe(
name=recipe_type.value,
quantization_recipe=None, # TODO - add quantization recipe
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: I have a PR updating CoreML docs that you might check out for quantization and available partitioner options: #13120

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metascroy it might make sense for you to follow up on the recipes for quant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, @abhinaykukkadapu are the quant recipes coming in a future PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metascroy If you want to follow up, i can leave it upto you as you have more context on quantization, or i can take it up if you don't have bandwidth.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add the quantization recipe, I'm happy to review the quantize_ and PT2E recipes you add for CoreML

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@metascroy sure will add in a follow up diff, can we go ahead with these changes?

lowering_recipe=lowering_recipe,
)

def _get_coreml_lowering_recipe(
self,
compute_precision: ct.precision,
**kwargs: Any,
) -> LoweringRecipe:
compile_specs = CoreMLBackend.generate_compile_specs(
compute_precision=compute_precision,
**kwargs,
)

minimum_deployment_target = kwargs.get("minimum_deployment_target", None)
take_over_mutable_buffer = True
if minimum_deployment_target and minimum_deployment_target < ct.target.iOS18:
take_over_mutable_buffer = False

partitioner = CoreMLPartitioner(
compile_specs=compile_specs,
take_over_mutable_buffer=take_over_mutable_buffer,
)

edge_compile_config = EdgeCompileConfig(
_check_ir_validity=False,
_skip_dim_order=False,
)

return LoweringRecipe(
partitioners=[partitioner], edge_compile_config=edge_compile_config
)
25 changes: 25 additions & 0 deletions backends/apple/coreml/recipes/coreml_recipe_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright © 2025 Apple Inc. All rights reserved.
#
# Please refer to the license found in the LICENSE file in the root directory of the source tree.


from executorch.export import RecipeType


COREML_BACKEND: str = "coreml"


class CoreMLRecipeType(RecipeType):
"""CoreML-specific generic recipe types"""

# FP32 generic recipe, defaults to values published by the CoreML backend and partitioner
# Precision = FP32, Default compute_unit = All (can be overriden by kwargs)
FP32 = "coreml_fp32"

# FP16 generic recipe, defaults to values published by the CoreML backend and partitioner
# Precision = FP32, Default compute_unit = All (can be overriden by kwargs)
FP16 = "coreml_fp16"

@classmethod
def get_backend_name(cls) -> str:
return COREML_BACKEND
Loading
Loading