hyperonnx 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hyperonnx/__init__.py +22 -0
- hyperonnx/auto.py +207 -0
- hyperonnx/exporter/__init__.py +51 -0
- hyperonnx/exporter/dynamo.py +286 -0
- hyperonnx/exporter/torchscript.py +134 -0
- hyperonnx/exporter/utils.py +143 -0
- hyperonnx/function_rewriter.py +544 -0
- hyperonnx/hyper_export.py +474 -0
- hyperonnx/patch.py +66 -0
- hyperonnx/torch_export.py +155 -0
- hyperonnx/typing.py +84 -0
- hyperonnx/utils.py +27 -0
- hyperonnx-1.0.0.dist-info/METADATA +154 -0
- hyperonnx-1.0.0.dist-info/RECORD +16 -0
- hyperonnx-1.0.0.dist-info/WHEEL +4 -0
- hyperonnx-1.0.0.dist-info/licenses/LICENSE +176 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (C) 2025 The HYPERONNX Authors.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from functools import update_wrapper
|
|
19
|
+
from typing import Dict, List
|
|
20
|
+
|
|
21
|
+
import torch
|
|
22
|
+
from torch import Tensor
|
|
23
|
+
from torch.nn import Module
|
|
24
|
+
from torch.utils.hooks import RemovableHandle
|
|
25
|
+
|
|
26
|
+
from ..typing import AnyTensor, ExportStatus, ModuleSpec
|
|
27
|
+
from .utils import plain_tensor_container
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def make_duck_forward(module_spec: ModuleSpec):
|
|
31
|
+
"""Make a duck forward function to replace this module with a duck type."""
|
|
32
|
+
|
|
33
|
+
class DuckForward(torch.autograd.Function): # pylint: disable=abstract-method
|
|
34
|
+
"""A duck forward function to export a custom op in onnx."""
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def forward(*args, **kwargs):
|
|
38
|
+
loop = module_spec["loops"]
|
|
39
|
+
if loop == 0:
|
|
40
|
+
output = module_spec.get("output")
|
|
41
|
+
else:
|
|
42
|
+
output = module_spec.get("loop_outputs")[loop - 1]
|
|
43
|
+
if module_spec.get("loop_outputs"):
|
|
44
|
+
module_spec["loops"] += 1
|
|
45
|
+
if isinstance(output, torch.Tensor) or output is None:
|
|
46
|
+
return output
|
|
47
|
+
plained_outputs = plain_tensor_container(output)
|
|
48
|
+
return tuple(i for i in plained_outputs if i is not None)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def backward(ctx, *grad_outputs):
|
|
52
|
+
raise RuntimeError("A duck forward function can't be backwarded.")
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def symbolic(g, *args):
|
|
56
|
+
"""Make onnx symbolic.
|
|
57
|
+
|
|
58
|
+
TODO:
|
|
59
|
+
This method is high likely to be changed in the future.
|
|
60
|
+
Refer to this link to use dynamo to export custom op:
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
output = module_spec.get("output")
|
|
64
|
+
type_name = module_spec["type_name"]
|
|
65
|
+
assert output is not None
|
|
66
|
+
if isinstance(output, Tensor):
|
|
67
|
+
outputs = 1
|
|
68
|
+
else:
|
|
69
|
+
outputs = len(plain_tensor_container(output))
|
|
70
|
+
# TODO: g.op doesn't accept None as one of inputs
|
|
71
|
+
# but we don't know the side effect of this WA.
|
|
72
|
+
vargs = filter(lambda x: x is not None, args)
|
|
73
|
+
op = g.op(type_name, *vargs, outputs=outputs)
|
|
74
|
+
return op
|
|
75
|
+
|
|
76
|
+
@torch.inference_mode()
|
|
77
|
+
def _forward(*args, **kwargs):
|
|
78
|
+
new_args = list(plain_tensor_container(args))
|
|
79
|
+
if kwargs:
|
|
80
|
+
# ISSUE (#158): caller kwargs order may not follow the signature order,
|
|
81
|
+
# to replace forward function with a duck type, we need to reverse the
|
|
82
|
+
# caller order to match the signature order.
|
|
83
|
+
sig = module_spec["signature"]
|
|
84
|
+
ordered_kwargs = {k: kwargs[k] for k in sig.parameters if k in kwargs}
|
|
85
|
+
new_args.extend(plain_tensor_container(ordered_kwargs))
|
|
86
|
+
# NOTE: apply doesn't take **kwargs, so we need to plain input container
|
|
87
|
+
# and convert objects to Tensor
|
|
88
|
+
return DuckForward.apply(*new_args)
|
|
89
|
+
|
|
90
|
+
return _forward
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@contextmanager
|
|
94
|
+
def replace_duck_forward(model: Module, module_spec: Dict[Module, ModuleSpec]):
|
|
95
|
+
"""Replace the forward function of modules in `module_spec` with a duck type.
|
|
96
|
+
|
|
97
|
+
It's used to laterly replace the duck type with the embedded onnx functions.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
model (Module): The torch module which is the top level of the model.
|
|
101
|
+
module_spec (Dict[Module, ModuleSpec]): The dictionary to store the spec of
|
|
102
|
+
modules. See :func:`make_hierarchical_hook` for more details.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def _hook_output_restore(
|
|
106
|
+
module: Module,
|
|
107
|
+
args: tuple, # pylint: disable=unused-argument
|
|
108
|
+
result: AnyTensor, # pylint: disable=unused-argument
|
|
109
|
+
):
|
|
110
|
+
spec = module_spec[module]
|
|
111
|
+
output = spec.get("output")
|
|
112
|
+
return output
|
|
113
|
+
|
|
114
|
+
handles: List[RemovableHandle] = []
|
|
115
|
+
try:
|
|
116
|
+
for child in filter(lambda c: c in module_spec, model.modules()):
|
|
117
|
+
if module_spec[child]["status"] == ExportStatus.EXPORTED:
|
|
118
|
+
setattr(child, "__ori_forward", child.forward)
|
|
119
|
+
if module_spec[child]["output_need_to_restore"]:
|
|
120
|
+
handle = child.register_forward_hook(_hook_output_restore)
|
|
121
|
+
handles.append(handle)
|
|
122
|
+
# makes child.forward keep the same signature as original forward
|
|
123
|
+
child.forward = update_wrapper(
|
|
124
|
+
make_duck_forward(module_spec[child]), child.forward
|
|
125
|
+
)
|
|
126
|
+
module_spec[child]["loops"] = 0
|
|
127
|
+
yield
|
|
128
|
+
finally:
|
|
129
|
+
for child in model.modules():
|
|
130
|
+
if getattr(child, "__ori_forward", None):
|
|
131
|
+
child.forward = getattr(child, "__ori_forward")
|
|
132
|
+
delattr(child, "__ori_forward")
|
|
133
|
+
for handle in handles:
|
|
134
|
+
handle.remove()
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (C) 2025 The HYPERONNX Authors.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from itertools import chain
|
|
18
|
+
from typing import Sequence, Tuple
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
from onnxifier.logger import warning
|
|
22
|
+
from torch import Tensor
|
|
23
|
+
|
|
24
|
+
from ..typing import AnyTensor, ModuleSpec
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def plain_tensor_container(obj: AnyTensor) -> Tuple[Tensor, ...]:
|
|
28
|
+
"""Iteratively flatten an arbitrary output of Tensors into a tuple of Tensors.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
obj (AnyTensor): Acceptable object could be a tuple, list, dict, or a nested
|
|
32
|
+
tuple, list, or dict of Tensors.
|
|
33
|
+
|
|
34
|
+
Examples::
|
|
35
|
+
|
|
36
|
+
obj = dict(a=[torch.rand(1), torch.rand(2)], b=dict(x=torch.ones(1)))
|
|
37
|
+
y = plain_tensor_container(obj)
|
|
38
|
+
# y => (torch.rand(1), torch.rand(2), torch.ones(1))
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If any object can't be recursively flattened.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Tuple[Tensor, ...]: a tuple of tensors.
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(obj, dict):
|
|
47
|
+
return plain_tensor_container(tuple(obj.values()))
|
|
48
|
+
elif isinstance(obj, Sequence):
|
|
49
|
+
if isinstance(obj, str):
|
|
50
|
+
# WA: for string type, return directly
|
|
51
|
+
return (obj,) # type: ignore
|
|
52
|
+
obj_seq = tuple(chain(*[plain_tensor_container(v) for v in obj]))
|
|
53
|
+
cls = type(obj)
|
|
54
|
+
try:
|
|
55
|
+
# restore tuple or list
|
|
56
|
+
return cls(obj_seq) # type: ignore
|
|
57
|
+
except Exception: # pylint: disable=broad-except
|
|
58
|
+
warning(
|
|
59
|
+
"During the flattening of module output, "
|
|
60
|
+
f"Can't construct {cls.__name__} from a tuple, we turn this output "
|
|
61
|
+
"into a pure tuple. However, this may lead to unexpected behavior in "
|
|
62
|
+
"forward function."
|
|
63
|
+
)
|
|
64
|
+
return obj_seq
|
|
65
|
+
elif isinstance(obj, Tensor):
|
|
66
|
+
return (obj,)
|
|
67
|
+
else: # may be constant POD
|
|
68
|
+
try:
|
|
69
|
+
return (torch.as_tensor(obj),)
|
|
70
|
+
except (RuntimeError, ValueError):
|
|
71
|
+
# I.e. None, Cache
|
|
72
|
+
return (obj,)
|
|
73
|
+
except Exception as ex:
|
|
74
|
+
raise ValueError(f"Unsupported output type: {type(obj)}") from ex
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def detach_module_outputs(output: AnyTensor, spec: ModuleSpec) -> AnyTensor:
|
|
78
|
+
"""Detach tensors in module outputs and record structural information.
|
|
79
|
+
|
|
80
|
+
This function recursively processes module outputs to detach tensors and
|
|
81
|
+
record the structural information needed for reconstruction. It handles
|
|
82
|
+
various output types including single tensors, dictionaries, sequences,
|
|
83
|
+
and None values.
|
|
84
|
+
|
|
85
|
+
Note:
|
|
86
|
+
If one of the output is None, it will be replaced with a tensor of False
|
|
87
|
+
to avoid breaking the torchscript forward function.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
output (AnyTensor): The module output to process. Can be a single tensor,
|
|
91
|
+
a dictionary of tensors, a sequence of tensors, or None.
|
|
92
|
+
spec (ModuleSpec): A specification dictionary to record structural
|
|
93
|
+
information. Will be modified in-place to store metadata about
|
|
94
|
+
the output structure.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
AnyTensor: The processed output with tensors detached. The structure
|
|
98
|
+
is preserved when possible, but may be converted to simpler types
|
|
99
|
+
(like tuple) if reconstruction fails.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
TypeError: If the output type is not supported (not Tensor, dict,
|
|
103
|
+
Sequence, or None).
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
if isinstance(output, Tensor):
|
|
107
|
+
return output.detach()
|
|
108
|
+
elif isinstance(output, dict):
|
|
109
|
+
spec["output_need_to_restore"] = True
|
|
110
|
+
dict_value = {k: detach_module_outputs(v, spec) for k, v in output.items()}
|
|
111
|
+
try:
|
|
112
|
+
cls = type(output)
|
|
113
|
+
return cls(**dict_value)
|
|
114
|
+
except Exception: # pylint: disable=broad-except
|
|
115
|
+
warning(
|
|
116
|
+
f"The original output type was {type(output).__name__}, "
|
|
117
|
+
"but failed to construct it from dict, so return as a dict directly."
|
|
118
|
+
)
|
|
119
|
+
return dict_value
|
|
120
|
+
elif isinstance(output, Sequence):
|
|
121
|
+
spec["output_need_to_restore"] = True
|
|
122
|
+
out_seq = tuple(detach_module_outputs(v, spec) for v in output)
|
|
123
|
+
cls = type(output)
|
|
124
|
+
try:
|
|
125
|
+
# restore tuple or list
|
|
126
|
+
return cls(out_seq) # type: ignore
|
|
127
|
+
except Exception: # pylint: disable=broad-except
|
|
128
|
+
warning(
|
|
129
|
+
"During the analysis of module output, "
|
|
130
|
+
f"Can't construct {cls.__name__} from a tuple, we turn this output "
|
|
131
|
+
"into a pure tuple. However, this may lead to unexpected behavior in "
|
|
132
|
+
"forward function."
|
|
133
|
+
)
|
|
134
|
+
return out_seq
|
|
135
|
+
elif output is None:
|
|
136
|
+
# return None in duck module is a bad case, because it will be treated as
|
|
137
|
+
# an output of the module, while None is not a valid torch IR, leads to
|
|
138
|
+
# an export failure in torch.
|
|
139
|
+
# However, this WA has a risk that codes like `if tensor is None: ...` will
|
|
140
|
+
# fail.
|
|
141
|
+
return torch.tensor(False)
|
|
142
|
+
else:
|
|
143
|
+
raise TypeError(f"Unsupported output type: {type(output)}")
|