Skip to content

Commit 218b5c9

Browse files
cymbalrushmergennachin
authored andcommitted
CoreML backend changes.
- CoreML backend related files are in the backends/coreml directory. Please refer to the setup.md files for the setup instructions. - There is an `examples/export/coreml_export_and_delegate.py` file that's used for delegating the Program to CoreML backend. - We added `ios-cmake` as a dependency for building the library but it should probably be added as a git submodule. Please let us know. - We added `.clang-format` for `Objective-C` files. - The root `CMakeLists.txt` file has the selective build option for building `coremldelegate` library.
1 parent c563f9d commit 218b5c9

File tree

92 files changed

+12924
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+12924
-0
lines changed

.github/workflows/pull.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,34 @@ jobs:
114114
# Test selective build
115115
PYTHON_EXECUTABLE=python bash examples/selective_build/test_selective_build.sh "${BUILD_TOOL}"
116116
117+
test-coreml-delegate:
118+
name: test-coreml-delegate
119+
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main
120+
secrets: inherit
121+
strategy:
122+
matrix:
123+
include:
124+
- build-tool: cmake
125+
fail-fast: false
126+
with:
127+
runner: macos-13-xlarge
128+
submodules: 'true'
129+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
130+
timeout: 60
131+
secrets-env: TOKEN_COREML_PRIVATE_REPO
132+
script: |
133+
WORKSPACE=$(pwd)
134+
pushd "${WORKSPACE}/pytorch/executorch"
135+
BUILD_TOOL=${{ matrix.build-tool }}
136+
# Setup MacOS dependencies as there is no Docker support on MacOS atm
137+
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-macos.sh "${BUILD_TOOL}"
138+
139+
# Build and test coreml delegate
140+
PYTHON_EXECUTABLE=python sh backends/apple/coreml/scripts/install_requirements_internal.sh
141+
PYTHON_EXECUTABLE=python sh backends/apple/coreml/scripts/build_tests.sh
142+
PYTHON_EXECUTABLE=python sh backends/apple/coreml/scripts/run_tests.sh
143+
popd
144+
117145
unittest:
118146
uses: ./.github/workflows/_unittest.yml
119147
with:

CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,19 @@ if(EXECUTORCH_BUILD_MPS)
346346
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/examples/apple/mps)
347347
endif()
348348

349+
# Build CoreML backend
350+
option(EXECUTORCH_BUILD_COREML "Build the backends/apple/coreml directory" OFF)
351+
if(EXECUTORCH_BUILD_COREML)
352+
# CoreML delegate library can only be built with iOS toolchain
353+
if(CMAKE_TOOLCHAIN_FILE MATCHES ".*(iOS\.)|(ios\.toolchain\.)cmake$")
354+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/backends/apple/coreml)
355+
else()
356+
message(
357+
FATAL_ERROR "executorch: Building CoreML delegate requires iOS toolchain"
358+
)
359+
endif()
360+
endif()
361+
349362
# Add selective build subdirectory
350363
if(BUILD_SELECTIVE_BUILD_TEST)
351364
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/examples/selective_build)

backends/apple/coreml/.clang-format

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
BasedOnStyle: WebKit
2+
BreakBeforeBraces: Attach
3+
AllowShortIfStatementsOnASingleLine: false
4+
BreakBeforeBinaryOperators: None
5+
BreakConstructorInitializers: BeforeColon
6+
IndentCaseLabels: true
7+
SortIncludes: true
8+
SpaceBeforeRangeBasedForLoopColon: false
9+
SpaceBeforeParens: ControlStatements
10+
AlignAfterOpenBracket: Align
11+
NamespaceIndentation: None
12+
MaxEmptyLinesToKeep: 2
13+
#AlignConsecutiveAssignments: true
14+
15+
ColumnLimit: 120
16+
17+
# When breaking up parameter/argument lists across multiple lines,
18+
# put only one per line instead of packing as many as possible.
19+
BinPackArguments: false
20+
BinPackParameters: false
21+
22+
# Control of individual brace wrapping cases.
23+
BreakBeforeBraces: Custom
24+
BraceWrapping:
25+
BeforeCatch: true
26+
27+
# Options for aligning backslashes in escaped newlines.
28+
AlignEscapedNewlines: Left
29+

backends/apple/coreml/.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Mac OS X
2+
*.DS_Store
3+
4+
cmake-out/
5+
6+
7+
# Xcode
8+
*.pbxuser
9+
*.mode1v3
10+
*.mode2v3
11+
*.perspectivev3
12+
*.xcuserstate
13+
project.xcworkspace/
14+
xcuserdata/
15+
16+
17+
runtime/runner/build/
18+
runtime/libraries/
19+
runtime/inmemoryfs/build/
20+
runtime/test/models/
21+
third-party/
22+
23+
xcode-build/
24+
25+
*.egg-info

backends/apple/coreml/CMakeLists.txt

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Copyright © 2023 Apple Inc. All rights reserved.
2+
3+
cmake_minimum_required(VERSION 3.19)
4+
5+
project(executorch_coreml_backend)
6+
7+
enable_language(CXX)
8+
enable_language(OBJC)
9+
10+
# Source root directory for executorch.
11+
if(NOT EXECUTORCH_ROOT)
12+
set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
13+
endif()
14+
15+
# inmemoryfs sources
16+
set(INMEMORYFS_SOURCES
17+
runtime/inmemoryfs/inmemory_filesystem.cpp
18+
runtime/inmemoryfs/memory_buffer.cpp
19+
runtime/inmemoryfs/memory_stream.cpp
20+
runtime/inmemoryfs/reversed_memory_stream.cpp
21+
)
22+
23+
# kvstore sources
24+
set(KVSTORE_SOURCES
25+
runtime/kvstore/database.cpp
26+
runtime/kvstore/sqlite_error.cpp
27+
runtime/kvstore/key_value_store.cpp
28+
runtime/kvstore/statement.cpp
29+
)
30+
31+
# delegate sources
32+
set(DELEGATE_SOURCES
33+
runtime/delegate/asset.mm
34+
runtime/delegate/backend_delegate.mm
35+
runtime/delegate/coreml_backend_delegate.mm
36+
runtime/delegate/ETCoreMLAsset.mm
37+
runtime/delegate/ETCoreMLAssetManager.mm
38+
runtime/delegate/ETCoreMLLogging.mm
39+
runtime/delegate/ETCoreMLModel.mm
40+
runtime/delegate/ETCoreMLModelManager.mm
41+
runtime/delegate/ETCoreMLStrings.mm
42+
runtime/delegate/MLModel_Prewarm.mm
43+
runtime/delegate/MLMultiArray_Copy.mm
44+
runtime/delegate/multiarray.mm
45+
runtime/delegate/serde_json.mm
46+
)
47+
48+
# Define the delegate library
49+
add_library(coremldelegate)
50+
target_sources(
51+
coremldelegate PRIVATE
52+
${INMEMORYFS_SOURCES}
53+
${KVSTORE_SOURCES}
54+
${DELEGATE_SOURCES}
55+
)
56+
target_include_directories(
57+
coremldelegate PRIVATE
58+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/include
59+
)
60+
target_include_directories(
61+
coremldelegate PRIVATE
62+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/kvstore
63+
)
64+
target_include_directories(
65+
coremldelegate PRIVATE
66+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/inmemoryfs
67+
)
68+
target_include_directories(
69+
coremldelegate PRIVATE
70+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/delegate
71+
)
72+
target_include_directories(
73+
coremldelegate PRIVATE
74+
${CMAKE_CURRENT_SOURCE_DIR}/third-party/nlohmann_json/single_include/nlohmann
75+
)
76+
target_include_directories(
77+
coremldelegate PRIVATE
78+
${EXECUTORCH_ROOT}/..
79+
)
80+
81+
target_link_libraries(
82+
coremldelegate PRIVATE
83+
executorch
84+
)
85+
86+
target_compile_options(coremldelegate PRIVATE "-fobjc-arc")
87+
target_compile_options(coremldelegate PRIVATE "-fno-exceptions")
88+
target_compile_options(coremldelegate PRIVATE "-fno-rtti")
89+
target_compile_definitions(coremldelegate PRIVATE JSON_NOEXCEPTION=1)
90+
91+
set(
92+
TARGET coremldelegate
93+
APPEND_STRING PROPERTY COMPILE_FLAGS "-x objective-c++"
94+
)
95+
96+
set(
97+
TARGET coremldelegate
98+
APPEND_STRING PROPERTY COMPILE_FLAGS "-Wno-null-character"
99+
)
100+
101+
set(
102+
TARGET coremldelegate
103+
APPEND_STRING PROPERTY COMPILE_FLAGS "-Wno-receiver-expr"
104+
)
105+
106+
set_property(
107+
TARGET coremldelegate
108+
PROPERTY CXX_STANDARD 17
109+
)

backends/apple/coreml/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# ExecuTorch CoreML Delegate
2+
3+
4+
This subtree contains the CoreML Delegate implementation for ExecuTorch.
5+
CoreML is an optimized framework for running machine learning models on Apple devices. The delegate is the mechanism for leveraging the CoreML framework to accelerate operators when running on Apple devices.
6+
7+
## Layout
8+
- `compiler/` : Lowers a module to CoreML backend.
9+
- `scripts/` : Scripts for installing dependencies and running tests.
10+
- `runtime/`: CoreML delegate runtime implementation.
11+
- `inmemoryfs`: InMemory filesystem implementation used to serialize/de-serialize AOT blob.
12+
- `kvstore`: Persistent Key-Value store implementation.
13+
- `delegate`: Runtime implementation.
14+
- `include` : Public headers.
15+
- `tests` : Tests for CoreML delegate.
16+
- `workspace` : Xcode workspace for tests.
17+
- `third-party/`: External dependencies.
18+
19+
## Help & Improvements
20+
If you have problems or questions or have suggestions for ways to make
21+
implementation and testing better, please create an issue on [github](https://www.github.com/pytorch/executorch/issues).
22+
23+
## Delegation
24+
25+
For delegating the Program to the **CoreML** backend, the client must be responsible for calling `to_backend` with the **CoreMLBackend** tag.
26+
27+
```python
28+
import executorch.exir as exir
29+
import torch
30+
31+
from executorch.exir.backend.backend_api import to_backend
32+
33+
from executorch.backends.coreml.compiler import CoreMLBackend
34+
35+
class LowerableSubModel(torch.nn.Module):
36+
def __init__(self):
37+
super().__init__()
38+
39+
def forward(self, x):
40+
return torch.sin(x)
41+
42+
# Convert the lowerable module to Edge IR Representation
43+
to_be_lowered = LowerableSubModel()
44+
example_input = (torch.ones(1), )
45+
to_be_lowered_exir_submodule = exir.capture(to_be_lowered, example_input).to_edge()
46+
47+
# Lower to CoreML backend
48+
lowered_module = to_backend('CoreMLBackend', to_be_lowered_exir_submodule, [])
49+
```
50+
51+
Currently, the **CoreML** backend delegates the whole module to **CoreML**. If a specific op is not supported by the **CoreML** backend then the `to_backend` call would throw an exception. We will be adding a **CoreML Partitioner** to resolve the issue.
52+
53+
The `to_backend` implementation is a thin wrapper over `coremltools`, `coremltools` is responsible for converting an **ExportedProgram** to a **MLModel**. The converted **MLModel** data is saved, flattened, and returned as bytes to **ExecuTorch**.
54+
55+
## Runtime
56+
57+
To execute a **CoreML** delegated **Program**, the client must link to the `coremldelegate` library. Once linked there are no additional steps required, **ExecuTorch** when running the **Program** would call the **CoreML** runtime to execute the **CoreML** delegated part of the **Program**.
58+
59+
Please follow the instructions described in the [CoreML setup](/backends/apple/coreml/setup.md) to link the `coremldelegate` library.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright © 2023 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+
from .coreml_preprocess import CoreMLBackend
6+
7+
__all__ = [
8+
CoreMLBackend,
9+
]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright © 2023 Apple Inc. All rights reserved.
2+
3+
# CoreML backend for delegating a EdgeProgram to CoreML.
4+
5+
import json
6+
import shutil
7+
import uuid
8+
9+
from pathlib import Path
10+
11+
from typing import final, List
12+
13+
import coremltools as ct
14+
import executorchcoreml
15+
16+
from executorch.exir.backend.backend_details import (
17+
BackendDetails,
18+
ExportedProgram,
19+
PreprocessResult,
20+
)
21+
from executorch.exir.backend.compile_spec_schema import CompileSpec
22+
23+
24+
@final
25+
class CoreMLBackend(BackendDetails):
26+
@staticmethod
27+
def to_bytes(mlmodel):
28+
dir_path = Path("tmp")
29+
model_dir_path = dir_path / "lowered_module"
30+
Path(model_dir_path).mkdir(parents=True, exist_ok=True)
31+
model_path = model_dir_path / "model.mlpackage"
32+
mlmodel.save(model_path)
33+
34+
# save model metdata
35+
spec = mlmodel.get_spec()
36+
input_names = [input.name for input in spec.description.input]
37+
output_names = [output.name for output in spec.description.output]
38+
identifier = uuid.uuid4()
39+
40+
model_metadata = {
41+
"inputNames": input_names,
42+
"outputNames": output_names,
43+
"identifier": str(identifier),
44+
}
45+
46+
# store metadata
47+
model_metadata_path = Path(model_dir_path) / "metadata.json"
48+
json_object = json.dumps(model_metadata)
49+
with open(model_metadata_path, "w") as outfile:
50+
outfile.write(json_object)
51+
52+
# flatten directory contents and convert it to bytes
53+
flattened_bytes = executorchcoreml.flatten_directory_contents(
54+
str(model_dir_path.resolve())
55+
)
56+
shutil.rmtree(str(model_dir_path.resolve()))
57+
return flattened_bytes
58+
59+
@classmethod
60+
# pyre-ignore
61+
def preprocess(
62+
cls,
63+
edge_program: ExportedProgram,
64+
module_compile_spec: List[CompileSpec],
65+
) -> PreprocessResult:
66+
mlmodel = ct.convert(
67+
model=edge_program,
68+
source="pytorch",
69+
convert_to="mlprogram",
70+
pass_pipeline=ct.PassPipeline.DEFAULT,
71+
skip_model_load=True,
72+
)
73+
flattened_bytes = CoreMLBackend.to_bytes(mlmodel)
74+
return PreprocessResult(
75+
processed_bytes=flattened_bytes,
76+
)

0 commit comments

Comments
 (0)