Skip to content

Commit edfec7e

Browse files
samhita-allawild-endeavor
authored andcommitted
ONNX Plugin (#804)
* fix isort errors Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * modified the plugin structure Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * resolve merge conflicts Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * added more parameters and cleaned up code Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * add readme Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * modified package names Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * update pythonbuild; add ONNXFile Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * wip Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * update requirements Signed-off-by: Samhita Alla <aallasamhita@gmail.com> * exclude tests on python 3.10 Signed-off-by: Samhita Alla <aallasamhita@gmail.com>
1 parent d76acdc commit edfec7e

28 files changed

Lines changed: 1564 additions & 0 deletions

File tree

.github/workflows/pythonbuild.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ jobs:
7777
- flytekit-kf-pytorch
7878
- flytekit-kf-tensorflow
7979
- flytekit-modin
80+
- flytekit-onnx-pytorch
81+
- flytekit-onnx-scikitlearn
82+
- flytekit-onnx-tensorflow
8083
- flytekit-pandera
8184
- flytekit-papermill
8285
- flytekit-polars
@@ -92,6 +95,14 @@ jobs:
9295
# https://github.com/great-expectations/great_expectations/blob/develop/setup.py#L87-L89
9396
- python-version: 3.10
9497
plugin-names: "flytekit-greatexpectations"
98+
# onnxruntime does not support python 3.10 yet
99+
# https://github.com/microsoft/onnxruntime/issues/9782
100+
- python-version: 3.10
101+
plugin-names: "flytekit-onnx-pytorch"
102+
- python-version: 3.10
103+
plugin-names: "flytekit-onnx-scikitlearn"
104+
- python-version: 3.10
105+
plugin-names: "flytekit-onnx-tensorflow"
95106
steps:
96107
- uses: actions/checkout@v2
97108
- name: Set up Python ${{ matrix.python-version }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ docs/source/plugins/generated/
3030
.pytest_flyte
3131
htmlcov
3232
*.ipynb
33+
*dat

flytekit/types/file/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@
7575
#: Can be used to receive or return a CSVFile. The underlying type is a FlyteFile type. This is just a
7676
#: decoration and useful for attaching content type information with the file and automatically documenting code.
7777
CSVFile = FlyteFile[csv]
78+
79+
onnx = typing.TypeVar("onnx")
80+
#: Can be used to receive or return an ONNXFile. The underlying type is a FlyteFile type. This is just a
81+
#: decoration and useful for attaching content type information with the file and automatically documenting code.
82+
ONNXFile = FlyteFile[onnx]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Flytekit ONNX PyTorch Plugin
2+
3+
This plugin allows you to generate ONNX models from your PyTorch models.
4+
5+
To install the plugin, run the following command:
6+
7+
```
8+
pip install flytekitplugins-onnxpytorch
9+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .schema import PyTorch2ONNX, PyTorch2ONNXConfig
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass, field
4+
from typing import Dict, List, Optional, Tuple, Type, Union
5+
6+
import torch
7+
from dataclasses_json import dataclass_json
8+
from torch.onnx import OperatorExportTypes, TrainingMode
9+
from typing_extensions import Annotated, get_args, get_origin
10+
11+
from flytekit import FlyteContext
12+
from flytekit.core.type_engine import TypeEngine, TypeTransformer, TypeTransformerFailedError
13+
from flytekit.models.core.types import BlobType
14+
from flytekit.models.literals import Blob, BlobMetadata, Literal, Scalar
15+
from flytekit.models.types import LiteralType
16+
from flytekit.types.file import ONNXFile
17+
18+
19+
@dataclass_json
20+
@dataclass
21+
class PyTorch2ONNXConfig:
22+
args: Union[Tuple, torch.Tensor]
23+
export_params: bool = True
24+
verbose: bool = False
25+
training: TrainingMode = TrainingMode.EVAL
26+
opset_version: int = 9
27+
input_names: List[str] = field(default_factory=list)
28+
output_names: List[str] = field(default_factory=list)
29+
operator_export_type: Optional[OperatorExportTypes] = None
30+
do_constant_folding: bool = False
31+
dynamic_axes: Union[Dict[str, Dict[int, str]], Dict[str, List[int]]] = field(default_factory=dict)
32+
keep_initializers_as_inputs: Optional[bool] = None
33+
custom_opsets: Dict[str, int] = field(default_factory=dict)
34+
export_modules_as_functions: Union[bool, set[Type]] = False
35+
36+
37+
@dataclass_json
38+
@dataclass
39+
class PyTorch2ONNX:
40+
model: Union[torch.nn.Module, torch.jit.ScriptModule, torch.jit.ScriptFunction] = field(default=None)
41+
42+
43+
def extract_config(t: Type[PyTorch2ONNX]) -> Tuple[Type[PyTorch2ONNX], PyTorch2ONNXConfig]:
44+
config = None
45+
if get_origin(t) is Annotated:
46+
base_type, config = get_args(t)
47+
if isinstance(config, PyTorch2ONNXConfig):
48+
return base_type, config
49+
else:
50+
raise TypeTransformerFailedError(f"{t}'s config isn't of type PyTorch2ONNXConfig")
51+
return t, config
52+
53+
54+
def to_onnx(ctx, model, config):
55+
local_path = ctx.file_access.get_random_local_path()
56+
57+
torch.onnx.export(
58+
model,
59+
**config,
60+
f=local_path,
61+
)
62+
63+
return local_path
64+
65+
66+
class PyTorch2ONNXTransformer(TypeTransformer[PyTorch2ONNX]):
67+
ONNX_FORMAT = "onnx"
68+
69+
def __init__(self):
70+
super().__init__(name="PyTorch ONNX", t=PyTorch2ONNX)
71+
72+
def get_literal_type(self, t: Type[PyTorch2ONNX]) -> LiteralType:
73+
return LiteralType(blob=BlobType(format=self.ONNX_FORMAT, dimensionality=BlobType.BlobDimensionality.SINGLE))
74+
75+
def to_literal(
76+
self,
77+
ctx: FlyteContext,
78+
python_val: PyTorch2ONNX,
79+
python_type: Type[PyTorch2ONNX],
80+
expected: LiteralType,
81+
) -> Literal:
82+
python_type, config = extract_config(python_type)
83+
84+
if config:
85+
local_path = to_onnx(ctx, python_val.model, config.__dict__.copy())
86+
remote_path = ctx.file_access.get_random_remote_path()
87+
ctx.file_access.put_data(local_path, remote_path, is_multipart=False)
88+
else:
89+
raise TypeTransformerFailedError(f"{python_type}'s config is None")
90+
91+
return Literal(
92+
scalar=Scalar(
93+
blob=Blob(
94+
uri=remote_path,
95+
metadata=BlobMetadata(
96+
type=BlobType(format=self.ONNX_FORMAT, dimensionality=BlobType.BlobDimensionality.SINGLE)
97+
),
98+
)
99+
)
100+
)
101+
102+
def to_python_value(
103+
self,
104+
ctx: FlyteContext,
105+
lv: Literal,
106+
expected_python_type: Type[ONNXFile],
107+
) -> ONNXFile:
108+
if not (lv.scalar.blob.uri and lv.scalar.blob.metadata.format == self.ONNX_FORMAT):
109+
raise TypeTransformerFailedError(f"ONNX format isn't of the expected type {expected_python_type}")
110+
111+
return ONNXFile(path=lv.scalar.blob.uri)
112+
113+
def guess_python_type(self, literal_type: LiteralType) -> Type[PyTorch2ONNX]:
114+
if (
115+
literal_type.blob is not None
116+
and literal_type.blob.dimensionality == BlobType.BlobDimensionality.SINGLE
117+
and literal_type.blob.format == self.ONNX_FORMAT
118+
):
119+
return PyTorch2ONNX
120+
121+
raise TypeTransformerFailedError(f"Transformer {self} cannot reverse {literal_type}")
122+
123+
124+
TypeEngine.register(PyTorch2ONNXTransformer())
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.
2+
-e file:.#egg=flytekitplugins-onnxpytorch
3+
onnxruntime
4+
pillow
5+
torchvision>=0.12.0
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#
2+
# This file is autogenerated by pip-compile with python 3.9
3+
# To update, run:
4+
#
5+
# pip-compile requirements.in
6+
#
7+
-e file:.#egg=flytekitplugins-onnxpytorch
8+
# via -r requirements.in
9+
arrow==1.2.2
10+
# via jinja2-time
11+
binaryornot==0.4.4
12+
# via cookiecutter
13+
certifi==2022.6.15
14+
# via requests
15+
cffi==1.15.1
16+
# via cryptography
17+
chardet==5.0.0
18+
# via binaryornot
19+
charset-normalizer==2.1.0
20+
# via requests
21+
click==8.1.3
22+
# via
23+
# cookiecutter
24+
# flytekit
25+
cloudpickle==2.1.0
26+
# via flytekit
27+
cookiecutter==2.1.1
28+
# via flytekit
29+
croniter==1.3.5
30+
# via flytekit
31+
cryptography==37.0.4
32+
# via pyopenssl
33+
dataclasses-json==0.5.7
34+
# via flytekit
35+
decorator==5.1.1
36+
# via retry
37+
deprecated==1.2.13
38+
# via flytekit
39+
diskcache==5.4.0
40+
# via flytekit
41+
docker==5.0.3
42+
# via flytekit
43+
docker-image-py==0.1.12
44+
# via flytekit
45+
docstring-parser==0.14.1
46+
# via flytekit
47+
flatbuffers==2.0
48+
# via onnxruntime
49+
flyteidl==1.1.8
50+
# via flytekit
51+
flytekit==1.1.0
52+
# via flytekitplugins-onnxpytorch
53+
googleapis-common-protos==1.56.3
54+
# via
55+
# flyteidl
56+
# grpcio-status
57+
grpcio==1.47.0
58+
# via
59+
# flytekit
60+
# grpcio-status
61+
grpcio-status==1.47.0
62+
# via flytekit
63+
idna==3.3
64+
# via requests
65+
importlib-metadata==4.12.0
66+
# via
67+
# flytekit
68+
# keyring
69+
jinja2==3.1.2
70+
# via
71+
# cookiecutter
72+
# jinja2-time
73+
jinja2-time==0.2.0
74+
# via cookiecutter
75+
keyring==23.6.0
76+
# via flytekit
77+
markupsafe==2.1.1
78+
# via jinja2
79+
marshmallow==3.17.0
80+
# via
81+
# dataclasses-json
82+
# marshmallow-enum
83+
# marshmallow-jsonschema
84+
marshmallow-enum==1.5.1
85+
# via dataclasses-json
86+
marshmallow-jsonschema==0.13.0
87+
# via flytekit
88+
mypy-extensions==0.4.3
89+
# via typing-inspect
90+
natsort==8.1.0
91+
# via flytekit
92+
numpy==1.23.0
93+
# via
94+
# onnxruntime
95+
# pandas
96+
# pyarrow
97+
# torchvision
98+
onnxruntime==1.11.1
99+
# via -r requirements.in
100+
packaging==21.3
101+
# via marshmallow
102+
pandas==1.4.3
103+
# via flytekit
104+
pillow==9.2.0
105+
# via
106+
# -r requirements.in
107+
# torchvision
108+
protobuf==3.20.1
109+
# via
110+
# flyteidl
111+
# flytekit
112+
# googleapis-common-protos
113+
# grpcio-status
114+
# onnxruntime
115+
# protoc-gen-swagger
116+
protoc-gen-swagger==0.1.0
117+
# via flyteidl
118+
py==1.11.0
119+
# via retry
120+
pyarrow==6.0.1
121+
# via flytekit
122+
pycparser==2.21
123+
# via cffi
124+
pyopenssl==22.0.0
125+
# via flytekit
126+
pyparsing==3.0.9
127+
# via packaging
128+
python-dateutil==2.8.2
129+
# via
130+
# arrow
131+
# croniter
132+
# flytekit
133+
# pandas
134+
python-json-logger==2.0.2
135+
# via flytekit
136+
python-slugify==6.1.2
137+
# via cookiecutter
138+
pytimeparse==1.1.8
139+
# via flytekit
140+
pytz==2022.1
141+
# via
142+
# flytekit
143+
# pandas
144+
pyyaml==6.0
145+
# via
146+
# cookiecutter
147+
# flytekit
148+
regex==2022.6.2
149+
# via docker-image-py
150+
requests==2.28.1
151+
# via
152+
# cookiecutter
153+
# docker
154+
# flytekit
155+
# responses
156+
# torchvision
157+
responses==0.21.0
158+
# via flytekit
159+
retry==0.9.2
160+
# via flytekit
161+
six==1.16.0
162+
# via
163+
# grpcio
164+
# python-dateutil
165+
sortedcontainers==2.4.0
166+
# via flytekit
167+
statsd==3.3.0
168+
# via flytekit
169+
text-unidecode==1.3
170+
# via python-slugify
171+
torch==1.12.0
172+
# via
173+
# flytekitplugins-onnxpytorch
174+
# torchvision
175+
torchvision==0.13.0
176+
# via -r requirements.in
177+
typing-extensions==4.3.0
178+
# via
179+
# flytekit
180+
# torch
181+
# torchvision
182+
# typing-inspect
183+
typing-inspect==0.7.1
184+
# via dataclasses-json
185+
urllib3==1.26.10
186+
# via
187+
# flytekit
188+
# requests
189+
# responses
190+
websocket-client==1.3.3
191+
# via docker
192+
wheel==0.37.1
193+
# via flytekit
194+
wrapt==1.14.1
195+
# via
196+
# deprecated
197+
# flytekit
198+
zipp==3.8.0
199+
# via importlib-metadata

0 commit comments

Comments
 (0)