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
29 changes: 24 additions & 5 deletions backends/apple/coreml/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,21 @@ runtime.python_library(
)

runtime.python_library(
name = "recipes",
srcs = glob([
"recipes/*.py",
]),
name = "coreml_recipes",
srcs = [
"recipes/__init__.py",
"recipes/coreml_recipe_provider.py"
],
visibility = [
"@EXECUTORCH_CLIENTS",
"//executorch/export/...",
],
deps = [
"fbsource//third-party/pypi/coremltools:coremltools",
":coreml_recipe_types",
":backend",
":partitioner",
":quantizer",
"//caffe2:torch",
"//executorch/exir:lib",
"//executorch/exir/backend:compile_spec_schema",
Expand All @@ -80,6 +85,20 @@ runtime.python_library(
],
)

runtime.python_library(
name = "coreml_recipe_types",
srcs = [
"recipes/coreml_recipe_types.py",
],
visibility = [
"@EXECUTORCH_CLIENTS",
"//executorch/export/...",
],
deps = [
"//executorch/export:recipe",
],
)

runtime.cxx_python_extension(
name = "executorchcoreml",
srcs = [
Expand Down Expand Up @@ -124,7 +143,7 @@ runtime.python_test(
"fbsource//third-party/pypi/pytest:pytest",
":partitioner",
":quantizer",
":recipes",
":coreml_recipes",
"//caffe2:torch",
"//pytorch/vision:torchvision",
"fbsource//third-party/pypi/scikit-learn:scikit-learn",
Expand Down
24 changes: 21 additions & 3 deletions backends/apple/coreml/recipes/coreml_recipe_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Please refer to the license found in the LICENSE file in the root directory of the source tree.


import logging
from typing import Any, Optional, Sequence

import coremltools as ct
Expand Down Expand Up @@ -111,8 +112,9 @@ def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> Non

unexpected = set(kwargs.keys()) - expected_keys
if unexpected:
raise ValueError(
f"Recipe '{recipe_type.value}' received unexpected parameters: {list(unexpected)}"
logging.warning(
f"CoreML recipe '{recipe_type.value}' ignoring unexpected parameters: {list(unexpected)}. "
f"Expected parameters: {list(expected_keys)}"
)

self._validate_base_parameters(kwargs)
Expand All @@ -121,7 +123,13 @@ def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> Non

def _get_expected_keys(self, recipe_type: RecipeType) -> set:
"""Get expected parameter keys for a recipe type"""
common_keys = {"minimum_deployment_target", "compute_unit"}
common_keys = {
"minimum_deployment_target",
"compute_unit",
"skip_ops_for_coreml_delegation",
"lower_full_graph",
"take_over_constant_data",
}

if recipe_type in [
CoreMLRecipeType.TORCHAO_INT4_WEIGHT_ONLY_PER_GROUP,
Expand Down Expand Up @@ -377,9 +385,19 @@ def _get_coreml_lowering_recipe(
if minimum_deployment_target and minimum_deployment_target < ct.target.iOS18:
take_over_mutable_buffer = False

# Extract additional partitioner parameters
skip_ops_for_coreml_delegation = kwargs.get(
"skip_ops_for_coreml_delegation", None
)
lower_full_graph = kwargs.get("lower_full_graph", False)
take_over_constant_data = kwargs.get("take_over_constant_data", True)

partitioner = CoreMLPartitioner(
compile_specs=compile_specs,
take_over_mutable_buffer=take_over_mutable_buffer,
skip_ops_for_coreml_delegation=skip_ops_for_coreml_delegation,
lower_full_graph=lower_full_graph,
take_over_constant_data=take_over_constant_data,
)

edge_compile_config = EdgeCompileConfig(
Expand Down
25 changes: 0 additions & 25 deletions backends/apple/coreml/test/test_coreml_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,6 @@ def test_int4_weight_only_per_group_validation(self):
)
self.assertIn("must be positive", str(cm.exception))

# Test unexpected parameter
with self.assertRaises(ValueError) as cm:
self.provider.create_recipe(
CoreMLRecipeType.TORCHAO_INT4_WEIGHT_ONLY_PER_CHANNEL,
group_size=32, # group_size not valid for per-channel
)
self.assertIn("unexpected parameters", str(cm.exception))

def test_int8_weight_only_per_channel(self):
"""Test INT8 weight-only per-channel quantization"""
model = TestHelperModules.TwoLinearModule().eval()
Expand Down Expand Up @@ -385,23 +377,6 @@ def forward(self, x):
self._compare_eager_quantized_model_outputs(session, example_inputs, atol=1e-2)
self._compare_eager_unquantized_model_outputs(session, model, example_inputs)

def test_pt2e_recipes_parameter_rejection(self):
"""Test that PT2E recipes reject TorchAO-specific parameters"""
# PT2E recipes should reject TorchAO-specific parameters
pt2e_recipes = [
CoreMLRecipeType.PT2E_INT8_STATIC,
CoreMLRecipeType.PT2E_INT8_WEIGHT_ONLY,
]
torchao_params = ["filter_fn", "group_size", "bits", "block_size"]

for recipe_type in pt2e_recipes:
for param in torchao_params:
with self.subTest(recipe=recipe_type.value, param=param):
kwargs = {param: "dummy_value"}
with self.assertRaises(ValueError) as cm:
self.provider.create_recipe(recipe_type, **kwargs)
self.assertIn("unexpected parameters", str(cm.exception).lower())

def test_filter_fn_comprehensive(self):
"""Comprehensive test for filter_fn parameter functionality"""

Expand Down
3 changes: 0 additions & 3 deletions backends/xnnpack/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ runtime.python_library(
],
deps = [
":xnnpack_preprocess",
"//executorch/export:lib",
"//executorch/backends/xnnpack/partition:xnnpack_partitioner",
"//executorch/backends/xnnpack/utils:xnnpack_utils",
"//executorch/backends/xnnpack/recipes:xnnpack_recipe_provider",
"//executorch/backends/xnnpack/recipes:xnnpack_recipe_types",
],
)
8 changes: 0 additions & 8 deletions backends/xnnpack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

from executorch.export import recipe_registry

# Exposed Partitioners in XNNPACK Package
from .partition.xnnpack_partitioner import (
XnnpackDynamicallyQuantizedPartitioner,
XnnpackPartitioner,
)
from .recipes.xnnpack_recipe_provider import XNNPACKRecipeProvider
from .recipes.xnnpack_recipe_types import XNNPackRecipeType

# Auto-register XNNPACK recipe provider
recipe_registry.register_backend_recipe_provider(XNNPACKRecipeProvider())

# Exposed Configs in XNNPACK Package
from .utils.configs import (
Expand All @@ -34,7 +27,6 @@
"XnnpackDynamicallyQuantizedPartitioner",
"XnnpackPartitioner",
"XnnpackBackend",
"XNNPackRecipeType",
"capture_graph_for_xnnpack",
"get_xnnpack_capture_config",
"get_xnnpack_edge_compile_config",
Expand Down
18 changes: 17 additions & 1 deletion backends/xnnpack/recipes/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@ load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")

oncall("executorch")

runtime.python_library(
name = "xnnpack_recipes",
srcs = [
"__init__.py",
],
visibility = [
"//executorch/...",
"@EXECUTORCH_CLIENTS",
],
deps = [
"//executorch/export:recipe_registry",
":xnnpack_recipe_provider",
":xnnpack_recipe_types",
],
)

runtime.python_library(
name = "xnnpack_recipe_provider",
srcs = [
Expand Down Expand Up @@ -30,6 +46,6 @@ runtime.python_library(
"@EXECUTORCH_CLIENTS",
],
deps = [
"//executorch/export:lib",
"//executorch/export:recipe",
],
)
19 changes: 19 additions & 0 deletions backends/xnnpack/recipes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.

from executorch.export import recipe_registry

from .xnnpack_recipe_provider import XNNPACKRecipeProvider
from .xnnpack_recipe_types import XNNPackRecipeType

# Auto-register XNNPACK recipe provider
recipe_registry.register_backend_recipe_provider(XNNPACKRecipeProvider())


__all__ = [
"XNNPACKRecipeProvider",
"XNNPackRecipeType",
]
13 changes: 7 additions & 6 deletions backends/xnnpack/recipes/xnnpack_recipe_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

# pyre-strict

import logging
from typing import Any, Optional, Sequence

import torch
Expand Down Expand Up @@ -180,9 +181,9 @@ def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> Non
expected_keys = {"group_size"}
unexpected = set(kwargs.keys()) - expected_keys
if unexpected:
raise ValueError(
f"Recipe '{recipe_type.value}' only accepts 'group_size' parameter. "
f"Unexpected parameters: {list(unexpected)}"
logging.warning(
f"XNNPACK recipe '{recipe_type.value}' ignoring unexpected parameters: {list(unexpected)}. "
f"Only 'group_size' is supported for this recipe."
)
if "group_size" in kwargs:
group_size = kwargs["group_size"]
Expand All @@ -193,7 +194,7 @@ def _validate_recipe_kwargs(self, recipe_type: RecipeType, **kwargs: Any) -> Non
elif kwargs:
# All other recipes don't expect any kwargs
unexpected = list(kwargs.keys())
raise ValueError(
f"Recipe '{recipe_type.value}' does not accept any parameters. "
f"Unexpected parameters: {unexpected}"
logging.warning(
f"XNNPACK recipe '{recipe_type.value}' ignoring unexpected parameters: {unexpected}. "
f"This recipe does not accept any parameters."
)
14 changes: 8 additions & 6 deletions backends/xnnpack/recipes/xnnpack_recipe_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@
class XNNPackRecipeType(RecipeType):
"""XNNPACK-specific recipe types"""

FP32 = "fp32"
FP32 = "xnnpack_fp32"

## PT2E-based quantization recipes
# INT8 Dynamic Quantization
PT2E_INT8_DYNAMIC_PER_CHANNEL = "pt2e_int8_dynamic_per_channel"
PT2E_INT8_DYNAMIC_PER_CHANNEL = "xnnpack_pt2e_int8_dynamic_per_channel"
# INT8 Static Quantization, needs calibration dataset
PT2E_INT8_STATIC_PER_CHANNEL = "pt2e_int8_static_per_channel"
PT2E_INT8_STATIC_PER_TENSOR = "pt2e_int8_static_per_tensor"
PT2E_INT8_STATIC_PER_CHANNEL = "xnnpack_pt2e_int8_static_per_channel"
PT2E_INT8_STATIC_PER_TENSOR = "xnnpack_pt2e_int8_static_per_tensor"

## TorchAO-based quantization recipes
# INT8 Dynamic Activations INT4 Weight Quantization, Axis = 0
TORCHAO_INT8_DYNAMIC_ACT_INT4_WEIGHT_PER_CHANNEL = (
"torchao_int8da_int4w_per_channel"
"xnnpack_torchao_int8da_int4w_per_channel"
)
# INT8 Dynamic Activations INT4 Weight Quantization, default group_size = 32
# can be overriden by group_size kwarg
TORCHAO_INT8_DYNAMIC_ACT_INT4_WEIGHT_PER_TENSOR = "torchao_int8da_int4w_per_tensor"
TORCHAO_INT8_DYNAMIC_ACT_INT4_WEIGHT_PER_TENSOR = (
"xnnpack_torchao_int8da_int4w_per_tensor"
)

@classmethod
def get_backend_name(cls) -> str:
Expand Down
2 changes: 1 addition & 1 deletion backends/xnnpack/test/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ runtime.python_test(
"HTTPS_PROXY": "http://fwdproxy:8080",
},
deps = [
"//executorch/backends/xnnpack:xnnpack_delegate",
"//executorch/backends/xnnpack/recipes:xnnpack_recipes",
"//executorch/export:lib",
"//pytorch/vision:torchvision", # @manual
"//executorch/backends/xnnpack/test/tester:tester",
Expand Down
9 changes: 0 additions & 9 deletions backends/xnnpack/test/recipes/test_xnnpack_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,6 @@ def test_all_models_with_recipes(self) -> None:
if os.path.exists("dog.jpg"):
os.remove("dog.jpg")

def test_validate_recipe_kwargs_fp32(self) -> None:
provider = XNNPACKRecipeProvider()

with self.assertRaises(ValueError) as cm:
provider.create_recipe(XNNPackRecipeType.FP32, invalid_param=123)

error_msg = str(cm.exception)
self.assertIn("Recipe 'fp32' does not accept any parameters", error_msg)

def test_validate_recipe_kwargs_int4_tensor_with_valid_group_size(
self,
) -> None:
Expand Down
13 changes: 13 additions & 0 deletions export/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,16 @@ runtime.python_library(
"types.py",
],
)

runtime.python_library(
name = "target_recipes",
srcs = [
"target_recipes.py",
],
deps = [
"fbsource//third-party/pypi/coremltools:coremltools",
"//executorch/export:recipe",
"//executorch/backends/xnnpack/recipes:xnnpack_recipes",
"//executorch/backends/apple/coreml:coreml_recipes",
]
)
Loading
Loading