onnx2tf 1.29.20__py3-none-any.whl → 1.29.22__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.
- onnx2tf/__init__.py +1 -1
- onnx2tf/onnx2tf.py +31 -2
- onnx2tf/ops/MaxPool.py +54 -0
- onnx2tf/ops/ScatterElements.py +50 -1
- onnx2tf/ops/Unique.py +71 -11
- onnx2tf/utils/common_functions.py +99 -2
- {onnx2tf-1.29.20.dist-info → onnx2tf-1.29.22.dist-info}/METADATA +29 -8
- {onnx2tf-1.29.20.dist-info → onnx2tf-1.29.22.dist-info}/RECORD +10 -10
- {onnx2tf-1.29.20.dist-info → onnx2tf-1.29.22.dist-info}/WHEEL +0 -0
- {onnx2tf-1.29.20.dist-info → onnx2tf-1.29.22.dist-info}/entry_points.txt +0 -0
onnx2tf/__init__.py
CHANGED
onnx2tf/onnx2tf.py
CHANGED
|
@@ -43,6 +43,7 @@ from typing import Optional, List, Any, Dict
|
|
|
43
43
|
from argparse import ArgumentParser
|
|
44
44
|
|
|
45
45
|
import importlib
|
|
46
|
+
import onnx2tf.utils.common_functions as common_functions
|
|
46
47
|
from onnx2tf.utils.common_functions import (
|
|
47
48
|
dummy_onnx_inference,
|
|
48
49
|
dummy_tf_inference,
|
|
@@ -639,6 +640,7 @@ def convert(
|
|
|
639
640
|
batch_size: Optional[int] = None,
|
|
640
641
|
overwrite_input_shape: Optional[List[str]] = None,
|
|
641
642
|
shape_hints: Optional[List[str]] = None,
|
|
643
|
+
value_hints: Optional[List[str]] = None,
|
|
642
644
|
no_large_tensor: Optional[bool] = False,
|
|
643
645
|
output_nms_with_dynamic_tensor: Optional[bool] = False,
|
|
644
646
|
switch_nms_version: Optional[str] = 'v4',
|
|
@@ -850,6 +852,15 @@ def convert(
|
|
|
850
852
|
A value of 1 or more must be specified.\n
|
|
851
853
|
Numerical values other than dynamic dimensions are ignored.
|
|
852
854
|
|
|
855
|
+
value_hints: Optional[List[str]]
|
|
856
|
+
Value hints for dummy inference input tensors.\n
|
|
857
|
+
The format is\n
|
|
858
|
+
["input_name_1:value","input_name_2:value","*:default_value"].\n
|
|
859
|
+
"*" applies to all inputs not explicitly specified.\n
|
|
860
|
+
Values are scalar only.\n
|
|
861
|
+
e.g.\n
|
|
862
|
+
['input0:0.5','mask:0','*:1.0']\n
|
|
863
|
+
|
|
853
864
|
no_large_tensor: Optional[bool]
|
|
854
865
|
Suppresses constant bloat caused by Tile OP when optimizing models in onnxsim.\n
|
|
855
866
|
See: https://github.com/daquexian/onnx-simplifier/issues/178
|
|
@@ -1110,6 +1121,8 @@ def convert(
|
|
|
1110
1121
|
if verbosity is None:
|
|
1111
1122
|
verbosity = 'debug'
|
|
1112
1123
|
set_log_level('error' if non_verbose else verbosity)
|
|
1124
|
+
common_functions.set_dummy_shape_hints(shape_hints)
|
|
1125
|
+
common_functions.set_dummy_value_hints(value_hints)
|
|
1113
1126
|
|
|
1114
1127
|
# Either designation required
|
|
1115
1128
|
if not input_onnx_file_path and not onnx_graph:
|
|
@@ -1326,6 +1339,10 @@ def convert(
|
|
|
1326
1339
|
'Failed to optimize the onnx file.'
|
|
1327
1340
|
)
|
|
1328
1341
|
|
|
1342
|
+
has_external_data = False
|
|
1343
|
+
if input_onnx_file_path and os.path.exists(input_onnx_file_path):
|
|
1344
|
+
has_external_data = check_has_external_data(input_onnx_file_path)
|
|
1345
|
+
|
|
1329
1346
|
# Automatic generation of each OP name - sng4onnx
|
|
1330
1347
|
if not not_use_opname_auto_generate:
|
|
1331
1348
|
info('')
|
|
@@ -1357,9 +1374,7 @@ def convert(
|
|
|
1357
1374
|
|
|
1358
1375
|
# Loading Graphs
|
|
1359
1376
|
# onnx_graph If specified, onnx_graph is processed first
|
|
1360
|
-
has_external_data = False
|
|
1361
1377
|
if not onnx_graph:
|
|
1362
|
-
has_external_data = check_has_external_data(input_onnx_file_path)
|
|
1363
1378
|
onnx_graph = onnx.load(input_onnx_file_path)
|
|
1364
1379
|
|
|
1365
1380
|
if not auto_split_model and onnx_graph is not None:
|
|
@@ -1686,6 +1701,7 @@ def convert(
|
|
|
1686
1701
|
'batch_size': batch_size,
|
|
1687
1702
|
'overwrite_input_shape': overwrite_input_shape,
|
|
1688
1703
|
'shape_hints': shape_hints,
|
|
1704
|
+
'value_hints': value_hints,
|
|
1689
1705
|
'no_large_tensor': no_large_tensor,
|
|
1690
1706
|
'output_nms_with_dynamic_tensor': output_nms_with_dynamic_tensor,
|
|
1691
1707
|
'switch_nms_version': switch_nms_version,
|
|
@@ -3874,6 +3890,18 @@ def main():
|
|
|
3874
3890
|
'Only applied to dynamic dimensions in inputs. \n' +
|
|
3875
3891
|
'Only used when -cotof or -coto are specified.'
|
|
3876
3892
|
)
|
|
3893
|
+
parser.add_argument(
|
|
3894
|
+
'-vh',
|
|
3895
|
+
'--value_hints',
|
|
3896
|
+
type=str,
|
|
3897
|
+
nargs='+',
|
|
3898
|
+
help=\
|
|
3899
|
+
'Value hints for dummy inference input tensors. \n' +
|
|
3900
|
+
'The format is\n' +
|
|
3901
|
+
'"input_name_1:value" "input_name_2:value" "*:default_value". \n' +
|
|
3902
|
+
'"*" applies to all inputs not explicitly specified. \n' +
|
|
3903
|
+
'Values are scalar only.'
|
|
3904
|
+
)
|
|
3877
3905
|
parser.add_argument(
|
|
3878
3906
|
'-nlt',
|
|
3879
3907
|
'--no_large_tensor',
|
|
@@ -4359,6 +4387,7 @@ def main():
|
|
|
4359
4387
|
batch_size=args.batch_size,
|
|
4360
4388
|
overwrite_input_shape=args.overwrite_input_shape,
|
|
4361
4389
|
shape_hints=args.shape_hints,
|
|
4390
|
+
value_hints=args.value_hints,
|
|
4362
4391
|
no_large_tensor=args.no_large_tensor,
|
|
4363
4392
|
output_nms_with_dynamic_tensor=args.output_nms_with_dynamic_tensor,
|
|
4364
4393
|
switch_nms_version=args.switch_nms_version,
|
onnx2tf/ops/MaxPool.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import random
|
|
2
2
|
random.seed(0)
|
|
3
3
|
import numpy as np
|
|
4
|
+
import itertools
|
|
4
5
|
np.random.seed(0)
|
|
5
6
|
import tensorflow as tf
|
|
6
7
|
import tf_keras
|
|
@@ -119,6 +120,59 @@ def make_node(
|
|
|
119
120
|
**kwargs,
|
|
120
121
|
)
|
|
121
122
|
|
|
123
|
+
# Guard: brute-force axis alignment between NCHW and NHWC when batch dim mismatches.
|
|
124
|
+
# Only trigger when shapes are fully known to avoid destabilizing existing behavior.
|
|
125
|
+
def _shape_matches(shape_a, shape_b):
|
|
126
|
+
if shape_a is None or shape_b is None or len(shape_a) != len(shape_b):
|
|
127
|
+
return False
|
|
128
|
+
for dim_a, dim_b in zip(shape_a, shape_b):
|
|
129
|
+
if dim_a is None or dim_b is None:
|
|
130
|
+
continue
|
|
131
|
+
if dim_a != dim_b:
|
|
132
|
+
return False
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def _best_perm_to_match(cur_shape, target_shape):
|
|
136
|
+
rank = len(cur_shape)
|
|
137
|
+
best_perm = None
|
|
138
|
+
best_cost = None
|
|
139
|
+
for perm in itertools.permutations(range(rank)):
|
|
140
|
+
permuted = [cur_shape[i] for i in perm]
|
|
141
|
+
if not _shape_matches(permuted, target_shape):
|
|
142
|
+
continue
|
|
143
|
+
cost = sum(abs(i - perm[i]) for i in range(rank))
|
|
144
|
+
if best_cost is None or cost < best_cost:
|
|
145
|
+
best_cost = cost
|
|
146
|
+
best_perm = perm
|
|
147
|
+
return best_perm
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
current_shape = input_tensor.shape.as_list()
|
|
151
|
+
except Exception:
|
|
152
|
+
current_shape = None
|
|
153
|
+
|
|
154
|
+
if onnx_input_shape is not None and current_shape is not None:
|
|
155
|
+
onnx_shape = [
|
|
156
|
+
dim if isinstance(dim, int) else None for dim in onnx_input_shape
|
|
157
|
+
]
|
|
158
|
+
cur_shape = [
|
|
159
|
+
dim if isinstance(dim, int) else None for dim in current_shape
|
|
160
|
+
]
|
|
161
|
+
if len(onnx_shape) in (3, 4, 5) \
|
|
162
|
+
and len(cur_shape) == len(onnx_shape) \
|
|
163
|
+
and None not in onnx_shape \
|
|
164
|
+
and None not in cur_shape:
|
|
165
|
+
expected_shape = [onnx_shape[0]] + onnx_shape[2:] + [onnx_shape[1]]
|
|
166
|
+
if cur_shape[0] != onnx_shape[0] \
|
|
167
|
+
and not _shape_matches(cur_shape, expected_shape):
|
|
168
|
+
perm = _best_perm_to_match(cur_shape, expected_shape)
|
|
169
|
+
if perm is not None:
|
|
170
|
+
input_tensor = transpose_with_flexing_deterrence(
|
|
171
|
+
input_tensor=input_tensor,
|
|
172
|
+
perm=list(perm),
|
|
173
|
+
**kwargs,
|
|
174
|
+
)
|
|
175
|
+
|
|
122
176
|
filter = None
|
|
123
177
|
|
|
124
178
|
auto_pad = graph_node.attrs.get('auto_pad', 'NOTSET')
|
onnx2tf/ops/ScatterElements.py
CHANGED
|
@@ -146,6 +146,48 @@ def make_node(
|
|
|
146
146
|
axis=axis,
|
|
147
147
|
indices=indices_tensor,
|
|
148
148
|
)
|
|
149
|
+
indices_rank = None
|
|
150
|
+
if hasattr(indices_tensor, "shape") and indices_tensor.shape is not None:
|
|
151
|
+
try:
|
|
152
|
+
indices_rank = len(indices_tensor.shape)
|
|
153
|
+
except TypeError:
|
|
154
|
+
indices_rank = indices_tensor.shape.rank
|
|
155
|
+
updates_rank = updates_tensor_rank
|
|
156
|
+
broadcast_shape = None
|
|
157
|
+
pad_rank = 0
|
|
158
|
+
|
|
159
|
+
def _pad_and_broadcast(target_tensor, pad_rank, target_shape):
|
|
160
|
+
tensor = target_tensor
|
|
161
|
+
if isinstance(tensor, np.ndarray):
|
|
162
|
+
tensor = tf.convert_to_tensor(tensor)
|
|
163
|
+
if pad_rank <= 0:
|
|
164
|
+
return tf.broadcast_to(tensor, target_shape)
|
|
165
|
+
tensor_shape = tf.shape(tensor)
|
|
166
|
+
new_shape = tf.concat(
|
|
167
|
+
[tf.ones([pad_rank], dtype=tf.int32), tensor_shape],
|
|
168
|
+
axis=0,
|
|
169
|
+
)
|
|
170
|
+
tensor = tf.reshape(tensor, new_shape)
|
|
171
|
+
return tf.broadcast_to(tensor, target_shape)
|
|
172
|
+
|
|
173
|
+
updates_tensor_for_scatter = updates_tensor
|
|
174
|
+
if indices_rank is not None and updates_rank is not None and indices_rank != updates_rank:
|
|
175
|
+
if indices_rank > updates_rank:
|
|
176
|
+
broadcast_shape = tf.shape(indices_tensor)
|
|
177
|
+
pad_rank = indices_rank - updates_rank
|
|
178
|
+
updates_tensor_for_scatter = _pad_and_broadcast(
|
|
179
|
+
updates_tensor,
|
|
180
|
+
pad_rank,
|
|
181
|
+
broadcast_shape,
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
broadcast_shape = tf.shape(updates_tensor)
|
|
185
|
+
pad_rank = updates_rank - indices_rank
|
|
186
|
+
indices_tensor = _pad_and_broadcast(
|
|
187
|
+
indices_tensor,
|
|
188
|
+
pad_rank,
|
|
189
|
+
broadcast_shape,
|
|
190
|
+
)
|
|
149
191
|
sparsified_dense_idx_shape = updates_tensor_shape
|
|
150
192
|
|
|
151
193
|
if None not in sparsified_dense_idx_shape:
|
|
@@ -160,6 +202,13 @@ def make_node(
|
|
|
160
202
|
]
|
|
161
203
|
|
|
162
204
|
idx_tensors_per_axis = tf.meshgrid(*idx_tensors_per_axis, indexing='ij')
|
|
205
|
+
if indices_rank is not None \
|
|
206
|
+
and updates_rank is not None \
|
|
207
|
+
and indices_rank > updates_rank:
|
|
208
|
+
idx_tensors_per_axis = [
|
|
209
|
+
_pad_and_broadcast(idx_tensor, pad_rank, broadcast_shape)
|
|
210
|
+
for idx_tensor in idx_tensors_per_axis
|
|
211
|
+
]
|
|
163
212
|
idx_tensors_per_axis[axis] = indices_tensor
|
|
164
213
|
dim_expanded_idx_tensors_per_axis = [
|
|
165
214
|
tf.expand_dims(idx_tensor, axis=-1)
|
|
@@ -194,7 +243,7 @@ def make_node(
|
|
|
194
243
|
)
|
|
195
244
|
|
|
196
245
|
indices = tf.reshape(coordinate, [-1, input_tensor_rank])
|
|
197
|
-
updates = tf.reshape(
|
|
246
|
+
updates = tf.reshape(updates_tensor_for_scatter, [-1])
|
|
198
247
|
output = tf.tensor_scatter_nd_update(
|
|
199
248
|
tensor=input_tensor,
|
|
200
249
|
indices=indices,
|
onnx2tf/ops/Unique.py
CHANGED
|
@@ -14,6 +14,7 @@ from onnx2tf.utils.common_functions import (
|
|
|
14
14
|
make_tf_node_info,
|
|
15
15
|
get_replacement_parameter,
|
|
16
16
|
pre_process_transpose,
|
|
17
|
+
convert_axis,
|
|
17
18
|
)
|
|
18
19
|
from onnx2tf.utils.logging import Color
|
|
19
20
|
|
|
@@ -69,8 +70,20 @@ def make_node(
|
|
|
69
70
|
**kwargs,
|
|
70
71
|
)
|
|
71
72
|
|
|
73
|
+
input_tensor_shape = input_tensor.shape
|
|
74
|
+
tensor_rank = len(input_tensor_shape) \
|
|
75
|
+
if input_tensor_shape != tf.TensorShape(None) else 1
|
|
76
|
+
|
|
72
77
|
axis = graph_node.attrs.get('axis', None)
|
|
73
78
|
sorted = graph_node.attrs.get('sorted', 1)
|
|
79
|
+
if axis is not None:
|
|
80
|
+
if isinstance(axis, np.ndarray) and axis.shape == ():
|
|
81
|
+
axis = int(axis)
|
|
82
|
+
axis = convert_axis(
|
|
83
|
+
axis=int(axis),
|
|
84
|
+
tensor_rank=tensor_rank,
|
|
85
|
+
before_op_output_shape_trans=before_op_output_shape_trans,
|
|
86
|
+
)
|
|
74
87
|
|
|
75
88
|
# Preserving Graph Structure (Dict)
|
|
76
89
|
for graph_node_output in graph_node_outputs:
|
|
@@ -101,17 +114,64 @@ def make_node(
|
|
|
101
114
|
|
|
102
115
|
# tf unique returns unsorted tensor, need to sort if option is enabled
|
|
103
116
|
if sorted:
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
# Sort unique outputs to match ONNX sorted behavior.
|
|
118
|
+
def _argsort_supported(dtype):
|
|
119
|
+
return dtype.is_floating or dtype.is_integer or dtype == tf.bool
|
|
120
|
+
|
|
121
|
+
y_rank = y.shape.rank
|
|
122
|
+
axis_ = axis
|
|
123
|
+
if axis_ is None:
|
|
124
|
+
axis_ = 0
|
|
125
|
+
if axis_ < 0 and y_rank is not None:
|
|
126
|
+
axis_ = axis_ + y_rank
|
|
127
|
+
|
|
128
|
+
def _lexsort_perm(flat_2d):
|
|
129
|
+
if not _argsort_supported(flat_2d.dtype):
|
|
130
|
+
return None
|
|
131
|
+
cols = flat_2d.shape[1]
|
|
132
|
+
if cols is None:
|
|
133
|
+
return None
|
|
134
|
+
order = tf.range(tf.shape(flat_2d)[0])
|
|
135
|
+
for col in reversed(range(cols)):
|
|
136
|
+
col_vals = tf.gather(flat_2d, order)[:, col]
|
|
137
|
+
if col_vals.dtype == tf.bool:
|
|
138
|
+
col_vals = tf.cast(col_vals, tf.int32)
|
|
139
|
+
order = tf.gather(order, tf.argsort(col_vals, stable=True))
|
|
140
|
+
return order
|
|
141
|
+
|
|
142
|
+
order = None
|
|
143
|
+
if y_rank is not None and y_rank == 1:
|
|
144
|
+
if _argsort_supported(y.dtype):
|
|
145
|
+
sort_vals = y
|
|
146
|
+
if sort_vals.dtype == tf.bool:
|
|
147
|
+
sort_vals = tf.cast(sort_vals, tf.int32)
|
|
148
|
+
order = tf.argsort(sort_vals, stable=True)
|
|
149
|
+
elif y_rank is not None and axis_ is not None and 0 <= axis_ < y_rank:
|
|
150
|
+
perm = [axis_] + [i for i in range(y_rank) if i != axis_]
|
|
151
|
+
y_t = tf.transpose(y, perm)
|
|
152
|
+
flat = tf.reshape(y_t, [tf.shape(y_t)[0], -1])
|
|
153
|
+
order = _lexsort_perm(flat)
|
|
154
|
+
|
|
155
|
+
if order is None:
|
|
156
|
+
warn_msg = f'' + \
|
|
157
|
+
Color.YELLOW(f'WARNING:') + ' ' + \
|
|
158
|
+
f'Unique sort fallback to unsorted due to dynamic shape or unsupported dtype.'
|
|
159
|
+
print(warn_msg)
|
|
160
|
+
else:
|
|
161
|
+
y = tf.gather(y, order, axis=axis_)
|
|
162
|
+
count = tf.gather(count, order)
|
|
163
|
+
indices = tf.gather(indices, order)
|
|
164
|
+
inv_order = tf.argsort(order)
|
|
165
|
+
inverse_indices = tf.gather(inv_order, inverse_indices)
|
|
166
|
+
|
|
167
|
+
if len(graph_node_outputs) >= 1:
|
|
168
|
+
tf_layers_dict[graph_node_outputs[0].name]['tf_node'] = y
|
|
169
|
+
if len(graph_node_outputs) >= 2:
|
|
170
|
+
tf_layers_dict[graph_node_outputs[1].name]['tf_node'] = indices
|
|
171
|
+
if len(graph_node_outputs) >= 3:
|
|
172
|
+
tf_layers_dict[graph_node_outputs[2].name]['tf_node'] = inverse_indices
|
|
173
|
+
if len(graph_node_outputs) >= 4:
|
|
174
|
+
tf_layers_dict[graph_node_outputs[3].name]['tf_node'] = count
|
|
115
175
|
|
|
116
176
|
# Generation of Debug Info
|
|
117
177
|
tf_outputs = {f"output{idx}": value for idx, value in enumerate([y, indices, inverse_indices, count])}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import math
|
|
2
|
+
import ast
|
|
2
3
|
import os
|
|
3
4
|
import io
|
|
4
5
|
import re
|
|
@@ -45,6 +46,59 @@ from onnx2tf.utils.enums import (
|
|
|
45
46
|
INF_INDEX_VALUE: int = 4294967296
|
|
46
47
|
ONNX_INF_INDEX_VALUE = sys.maxsize # 9223372036854775807
|
|
47
48
|
|
|
49
|
+
_DEFAULT_DUMMY_SHAPE_HINTS: Optional[List[str]] = None
|
|
50
|
+
_DEFAULT_DUMMY_VALUE_HINTS: Optional[List[str]] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def set_dummy_shape_hints(shape_hints: Optional[List[str]]) -> None:
|
|
54
|
+
global _DEFAULT_DUMMY_SHAPE_HINTS
|
|
55
|
+
_DEFAULT_DUMMY_SHAPE_HINTS = shape_hints
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def set_dummy_value_hints(value_hints: Optional[List[str]]) -> None:
|
|
59
|
+
global _DEFAULT_DUMMY_VALUE_HINTS
|
|
60
|
+
_DEFAULT_DUMMY_VALUE_HINTS = value_hints
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_value_hint_scalar(value: str) -> Optional[Any]:
|
|
64
|
+
try:
|
|
65
|
+
parsed = ast.literal_eval(value)
|
|
66
|
+
except Exception:
|
|
67
|
+
try:
|
|
68
|
+
parsed = float(value)
|
|
69
|
+
except Exception:
|
|
70
|
+
return None
|
|
71
|
+
if isinstance(parsed, (list, tuple, dict, set, np.ndarray)):
|
|
72
|
+
return None
|
|
73
|
+
if isinstance(parsed, (int, float, bool, np.number)):
|
|
74
|
+
return parsed
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _parse_value_hints(
|
|
79
|
+
value_hints: Optional[List[str]]
|
|
80
|
+
) -> Tuple[Dict[str, Any], Optional[Any], bool]:
|
|
81
|
+
if not value_hints:
|
|
82
|
+
return {}, None, False
|
|
83
|
+
hints: Dict[str, Any] = {}
|
|
84
|
+
default_value: Optional[Any] = None
|
|
85
|
+
for hint in value_hints:
|
|
86
|
+
if not isinstance(hint, str):
|
|
87
|
+
continue
|
|
88
|
+
parts = hint.split(':', 1)
|
|
89
|
+
if len(parts) != 2:
|
|
90
|
+
continue
|
|
91
|
+
input_name, value_str = parts[0], parts[1]
|
|
92
|
+
parsed_value = _parse_value_hint_scalar(value_str)
|
|
93
|
+
if parsed_value is None:
|
|
94
|
+
warn(f'Invalid --value_hints entry ignored: {hint}')
|
|
95
|
+
continue
|
|
96
|
+
if input_name == '*':
|
|
97
|
+
default_value = parsed_value
|
|
98
|
+
else:
|
|
99
|
+
hints[input_name] = parsed_value
|
|
100
|
+
return hints, default_value, default_value is not None
|
|
101
|
+
|
|
48
102
|
|
|
49
103
|
|
|
50
104
|
|
|
@@ -3854,6 +3908,7 @@ def dummy_onnx_inference(
|
|
|
3854
3908
|
enable_ort_output_memmap: bool = False,
|
|
3855
3909
|
ort_output_memmap_dir: Optional[str] = None,
|
|
3856
3910
|
shape_hints: Optional[List[str]] = None,
|
|
3911
|
+
value_hints: Optional[List[str]] = None,
|
|
3857
3912
|
input_datas_for_validation: Optional[Dict[str, np.ndarray]] = None,
|
|
3858
3913
|
) -> List[np.ndarray]:
|
|
3859
3914
|
"""Perform inference on ONNX subgraphs with an all-1 dummy tensor.
|
|
@@ -3891,6 +3946,10 @@ def dummy_onnx_inference(
|
|
|
3891
3946
|
Directory to store memmap files. If not specified, a temporary
|
|
3892
3947
|
directory is created and removed on exit.
|
|
3893
3948
|
|
|
3949
|
+
value_hints: Optional[List[str]]
|
|
3950
|
+
Value hints for dummy inference input tensors.
|
|
3951
|
+
Format: ["input_name:value", "*:default_value"].
|
|
3952
|
+
|
|
3894
3953
|
input_datas_for_validation: Optional[Dict[str, np.ndarray]]
|
|
3895
3954
|
Optional dict to be filled with the input tensors used for inference.
|
|
3896
3955
|
|
|
@@ -3899,6 +3958,11 @@ def dummy_onnx_inference(
|
|
|
3899
3958
|
outputs: List[np.ndarray]
|
|
3900
3959
|
Results of inference using dummy tensor
|
|
3901
3960
|
"""
|
|
3961
|
+
if shape_hints is None:
|
|
3962
|
+
shape_hints = _DEFAULT_DUMMY_SHAPE_HINTS
|
|
3963
|
+
if value_hints is None:
|
|
3964
|
+
value_hints = _DEFAULT_DUMMY_VALUE_HINTS
|
|
3965
|
+
|
|
3902
3966
|
# Separate onnx at specified output_names position
|
|
3903
3967
|
domain: str = onnx_graph.domain
|
|
3904
3968
|
ir_version: int = onnx_graph.ir_version
|
|
@@ -4053,6 +4117,7 @@ def dummy_onnx_inference(
|
|
|
4053
4117
|
name: tuple(size) for name, size in zip(input_names, input_sizes)
|
|
4054
4118
|
}
|
|
4055
4119
|
input_datas = {}
|
|
4120
|
+
value_hints_dict, default_value, has_default = _parse_value_hints(value_hints)
|
|
4056
4121
|
|
|
4057
4122
|
# -cid
|
|
4058
4123
|
if custom_input_op_name_np_data_path:
|
|
@@ -4086,7 +4151,17 @@ def dummy_onnx_inference(
|
|
|
4086
4151
|
|
|
4087
4152
|
else:
|
|
4088
4153
|
for input_name, input_size, input_dtype in zip(input_names, input_sizes, input_dtypes):
|
|
4089
|
-
|
|
4154
|
+
hint_value = value_hints_dict.get(
|
|
4155
|
+
input_name,
|
|
4156
|
+
default_value if has_default else None,
|
|
4157
|
+
)
|
|
4158
|
+
if hint_value is not None:
|
|
4159
|
+
input_datas[input_name] = np.full(
|
|
4160
|
+
input_size,
|
|
4161
|
+
hint_value,
|
|
4162
|
+
dtype=input_dtype,
|
|
4163
|
+
)
|
|
4164
|
+
elif test_data_nhwc is None:
|
|
4090
4165
|
input_datas[input_name] = np.ones(
|
|
4091
4166
|
input_size,
|
|
4092
4167
|
dtype=input_dtype,
|
|
@@ -4245,6 +4320,7 @@ def dummy_tf_inference(
|
|
|
4245
4320
|
verification_datas: Optional[List[np.ndarray]] = None,
|
|
4246
4321
|
custom_input_op_name_np_data_path: Optional[str] = None,
|
|
4247
4322
|
shape_hints: Optional[List[str]] = None,
|
|
4323
|
+
value_hints: Optional[List[str]] = None,
|
|
4248
4324
|
input_datas_for_validation: Optional[Dict[str, np.ndarray]] = None,
|
|
4249
4325
|
prefilled_input_datas: Optional[Dict[str, np.ndarray]] = None,
|
|
4250
4326
|
keep_shape_absolutely_input_names: Optional[List[str]] = None,
|
|
@@ -4269,6 +4345,11 @@ def dummy_tf_inference(
|
|
|
4269
4345
|
|
|
4270
4346
|
custom_input_op_name_np_data_path
|
|
4271
4347
|
Path to Numpy file for custom data used for dummy inference
|
|
4348
|
+
|
|
4349
|
+
value_hints: Optional[List[str]]
|
|
4350
|
+
Value hints for dummy inference input tensors.
|
|
4351
|
+
Format: ["input_name:value", "*:default_value"].
|
|
4352
|
+
|
|
4272
4353
|
input_datas_for_validation: Optional[Dict[str, np.ndarray]]
|
|
4273
4354
|
Optional dict to be filled with the input tensors used for inference.
|
|
4274
4355
|
|
|
@@ -4278,6 +4359,11 @@ def dummy_tf_inference(
|
|
|
4278
4359
|
Results of inference using dummy tensor.
|
|
4279
4360
|
Dict of tensorflow node and corresponding ndarray output.
|
|
4280
4361
|
"""
|
|
4362
|
+
if shape_hints is None:
|
|
4363
|
+
shape_hints = _DEFAULT_DUMMY_SHAPE_HINTS
|
|
4364
|
+
if value_hints is None:
|
|
4365
|
+
value_hints = _DEFAULT_DUMMY_VALUE_HINTS
|
|
4366
|
+
|
|
4281
4367
|
input_names: List[str] = [inp.name for inp in inputs]
|
|
4282
4368
|
input_sizes: List[int] = [inp.shape for inp in inputs]
|
|
4283
4369
|
input_size_map = {name: size for name, size in zip(input_names, input_sizes)}
|
|
@@ -4353,6 +4439,7 @@ def dummy_tf_inference(
|
|
|
4353
4439
|
|
|
4354
4440
|
input_dtypes: List[Any] = [inp.dtype for inp in inputs]
|
|
4355
4441
|
input_datas = {}
|
|
4442
|
+
value_hints_dict, default_value, has_default = _parse_value_hints(value_hints)
|
|
4356
4443
|
|
|
4357
4444
|
# -cid
|
|
4358
4445
|
if custom_input_op_name_np_data_path:
|
|
@@ -4409,7 +4496,17 @@ def dummy_tf_inference(
|
|
|
4409
4496
|
else:
|
|
4410
4497
|
if verification_datas is None:
|
|
4411
4498
|
for input_name, input_size, input_dtype in zip(input_names, input_sizes, input_dtypes):
|
|
4412
|
-
|
|
4499
|
+
hint_value = value_hints_dict.get(
|
|
4500
|
+
input_name,
|
|
4501
|
+
default_value if has_default else None,
|
|
4502
|
+
)
|
|
4503
|
+
if hint_value is not None:
|
|
4504
|
+
input_datas[input_name] = np.full(
|
|
4505
|
+
input_size,
|
|
4506
|
+
hint_value,
|
|
4507
|
+
dtype=TF_DTYPES_TO_NUMPY_DTYPES[input_dtype],
|
|
4508
|
+
)
|
|
4509
|
+
elif test_data_nhwc is None:
|
|
4413
4510
|
input_datas[input_name] = np.ones(
|
|
4414
4511
|
input_size,
|
|
4415
4512
|
dtype=TF_DTYPES_TO_NUMPY_DTYPES[input_dtype],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: onnx2tf
|
|
3
|
-
Version: 1.29.
|
|
3
|
+
Version: 1.29.22
|
|
4
4
|
Summary: Self-Created Tools to convert ONNX files (NCHW) to TensorFlow/TFLite/Keras format (NHWC). The purpose of this tool is to solve the massive Transpose extrapolation problem in onnx-tensorflow (onnx-tf).
|
|
5
5
|
Keywords: onnx,tensorflow,tflite,keras,deep-learning,machine-learning
|
|
6
6
|
Author: Katsuya Hyodo
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
18
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
19
|
Requires-Dist: requests==2.32.5
|
|
20
20
|
Requires-Dist: numpy==1.26.4
|
|
21
|
-
Requires-Dist: onnx==1.19.
|
|
21
|
+
Requires-Dist: onnx==1.19.1
|
|
22
22
|
Requires-Dist: onnxruntime==1.23.0
|
|
23
23
|
Requires-Dist: opencv-python==4.11.0.86
|
|
24
24
|
Requires-Dist: onnxsim==0.4.36
|
|
@@ -365,7 +365,7 @@ Video speed is adjusted approximately 50 times slower than actual speed.
|
|
|
365
365
|
docker run --rm -it \
|
|
366
366
|
-v `pwd`:/workdir \
|
|
367
367
|
-w /workdir \
|
|
368
|
-
ghcr.io/pinto0309/onnx2tf:1.29.
|
|
368
|
+
ghcr.io/pinto0309/onnx2tf:1.29.22
|
|
369
369
|
|
|
370
370
|
or
|
|
371
371
|
|
|
@@ -373,18 +373,18 @@ Video speed is adjusted approximately 50 times slower than actual speed.
|
|
|
373
373
|
docker run --rm -it \
|
|
374
374
|
-v `pwd`:/workdir \
|
|
375
375
|
-w /workdir \
|
|
376
|
-
docker.io/pinto0309/onnx2tf:1.29.
|
|
376
|
+
docker.io/pinto0309/onnx2tf:1.29.22
|
|
377
377
|
|
|
378
378
|
or
|
|
379
379
|
|
|
380
|
-
pip install -U onnx==1.19.
|
|
380
|
+
pip install -U onnx==1.19.1 \
|
|
381
381
|
&& pip install -U onnx-graphsurgeon==0.5.8 \
|
|
382
382
|
&& pip install -U onnxruntime==1.23.0 \
|
|
383
383
|
&& pip install -U onnxsim==0.4.36 \
|
|
384
384
|
&& pip install -U onnxoptimizer==0.4.2 \
|
|
385
385
|
&& pip install -U simple_onnx_processing_tools==1.1.32 \
|
|
386
|
-
&& pip install -U sne4onnx
|
|
387
|
-
&& pip install -U sng4onnx
|
|
386
|
+
&& pip install -U sne4onnx==1.0.15 \
|
|
387
|
+
&& pip install -U sng4onnx==1.0.5 \
|
|
388
388
|
&& pip install -U ai_edge_litert==1.2.0 \
|
|
389
389
|
&& pip install -U tensorflow==2.19.0 \
|
|
390
390
|
&& pip install -U protobuf==3.20.3 \
|
|
@@ -630,7 +630,7 @@ After many upgrades, the need for JSON parameter correction has become much less
|
|
|
630
630
|
|
|
631
631
|
`-ois` an option to overwrite the input OP to a static size if it has undefined dimensions. `-cotof` option checks the accuracy of all OPs one by one. `-cotoa` is the error value of the threshold for determining an accuracy error. If there are undefined dimensions in the input OP, it is better to fix them to the static geometry to improve the accuracy of the accuracy measurement.
|
|
632
632
|
|
|
633
|
-
Also, you can use the `-cind` option to specify custom input for `-cotof`, instead of using the default dummy input. Otherwise, all input values will be set to 1. For more information about the `-cind` option, please refer to [here](#cli-parameter).
|
|
633
|
+
Also, you can use the `-cind` option to specify custom input for `-cotof`, instead of using the default dummy input. Otherwise, all input values will be set to 1. You can override the dummy input values with `--value_hints` (scalar only, `*:default` supported). For more information about the `-cind` option, please refer to [here](#cli-parameter).
|
|
634
634
|
|
|
635
635
|
The `-cotof` option only compares the original ONNX and converted TensorFlow (Keras) models at Float32 precision, not at Float16 or INT8 precision.
|
|
636
636
|
|
|
@@ -644,6 +644,10 @@ onnx2tf -i mobilenetv2-12.onnx -b 1 -cotof -cotoa 1e-1
|
|
|
644
644
|
or
|
|
645
645
|
|
|
646
646
|
onnx2tf -i mobilenetv2-12.onnx -cotof -cotoa 1e-1 -cind "input" "/your/path/x.npy"
|
|
647
|
+
|
|
648
|
+
or
|
|
649
|
+
|
|
650
|
+
onnx2tf -i mobilenetv2-12.onnx -cotof -cotoa 1e-1 --value_hints "input:0.5" "*:1.0"
|
|
647
651
|
```
|
|
648
652
|

|
|
649
653
|
|
|
@@ -1826,6 +1830,14 @@ optional arguments:
|
|
|
1826
1830
|
A value of 1 or more must be specified.
|
|
1827
1831
|
Numerical values other than dynamic dimensions are ignored.
|
|
1828
1832
|
|
|
1833
|
+
-vh VALUE_HINTS [VALUE_HINTS ...], \
|
|
1834
|
+
--value_hints VALUE_HINTS [VALUE_HINTS ...]
|
|
1835
|
+
Value hints for dummy inference input tensors.
|
|
1836
|
+
The format is
|
|
1837
|
+
"input_name_1:value" "input_name_2:value" "*:default_value"
|
|
1838
|
+
"*" applies to all inputs not explicitly specified.
|
|
1839
|
+
Values are scalar only.
|
|
1840
|
+
|
|
1829
1841
|
-nlt, --no_large_tensor
|
|
1830
1842
|
Suppresses constant bloat caused by Tile OP when optimizing models in onnxsim.
|
|
1831
1843
|
See: https://github.com/daquexian/onnx-simplifier/issues/178
|
|
@@ -2157,6 +2169,7 @@ convert(
|
|
|
2157
2169
|
batch_size: Union[int, NoneType] = None,
|
|
2158
2170
|
overwrite_input_shape: Union[List[str], NoneType] = None,
|
|
2159
2171
|
shape_hints: Union[List[str], NoneType] = None,
|
|
2172
|
+
value_hints: Union[List[str], NoneType] = None,
|
|
2160
2173
|
no_large_tensor: Optional[bool] = False,
|
|
2161
2174
|
output_nms_with_dynamic_tensor: Optional[bool] = False,
|
|
2162
2175
|
switch_nms_version: Optional[str] = 'v4',
|
|
@@ -2377,6 +2390,13 @@ convert(
|
|
|
2377
2390
|
A value of 1 or more must be specified.
|
|
2378
2391
|
Numerical values other than dynamic dimensions are ignored.
|
|
2379
2392
|
|
|
2393
|
+
value_hints: Optional[List[str]]
|
|
2394
|
+
Value hints for dummy inference input tensors.
|
|
2395
|
+
The format is
|
|
2396
|
+
['input_name_1:value', 'input_name_2:value', '*:default_value']
|
|
2397
|
+
"*" applies to all inputs not explicitly specified.
|
|
2398
|
+
Values are scalar only.
|
|
2399
|
+
|
|
2380
2400
|
no_large_tensor: Optional[bool]
|
|
2381
2401
|
Suppresses constant bloat caused by Tile OP when optimizing models in onnxsim.
|
|
2382
2402
|
See: https://github.com/daquexian/onnx-simplifier/issues/178
|
|
@@ -3032,6 +3052,7 @@ The above differences often cannot be dealt with by simply converting the model
|
|
|
3032
3052
|
14. [nobuco](https://github.com/AlexanderLutsenko/nobuco)
|
|
3033
3053
|
15. [onnx2torch](https://github.com/ENOT-AutoDL/onnx2torch)
|
|
3034
3054
|
16. [ai-edge-torch](https://github.com/google-ai-edge/ai-edge-torch)
|
|
3055
|
+
17. [LiteRT.js](https://ai.google.dev/edge/litert/web)
|
|
3035
3056
|
|
|
3036
3057
|
## Acknowledgement
|
|
3037
3058
|
1. https://github.com/onnx/models
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
onnx2tf/__init__.py,sha256=
|
|
1
|
+
onnx2tf/__init__.py,sha256=EUJwryuQmTqI1Huqr9hrm1fJwxQqmGxUjAFgGvuB950,67
|
|
2
2
|
onnx2tf/__main__.py,sha256=2RSCQ7d4lc6CwD-rlGn9UicPFg-P5du7ZD_yh-kuBEU,57
|
|
3
|
-
onnx2tf/onnx2tf.py,sha256=
|
|
3
|
+
onnx2tf/onnx2tf.py,sha256=BC-BFMf8QUG7PtOvpwglhe1sc4FhTO8AMrdlxKUN5jc,208204
|
|
4
4
|
onnx2tf/ops/Abs.py,sha256=V7btmCG_ZvK_qJovUsguq0ZMJ349mhNQ4FHSgzP_Yuo,4029
|
|
5
5
|
onnx2tf/ops/Acos.py,sha256=Fo8YkFKuWq8Fi2xUrBdKcAH1yJ8r5pjSD0wgLttTNdk,4003
|
|
6
6
|
onnx2tf/ops/Acosh.py,sha256=ATQj2cT5JS_mTfXi0kXqJ1yzSZu5J0zHA5VjV3j7uKY,3588
|
|
@@ -98,7 +98,7 @@ onnx2tf/ops/LpPool.py,sha256=96eI1FaDgW0M_USWBCHFedvtojHTLL28_lb3mcEV55A,10470
|
|
|
98
98
|
onnx2tf/ops/MatMul.py,sha256=X9cQSD4BCogkDP6D4YZEZmOWnsceGL8ppN8E4kqyjB0,23926
|
|
99
99
|
onnx2tf/ops/MatMulInteger.py,sha256=qHqzdJNI9SeJDbW8pR90baYCdGN6FdOez4hi9EzwXoc,6538
|
|
100
100
|
onnx2tf/ops/Max.py,sha256=w5nMciO_6ApYUobHuwMGuS3xhuza7eSvKDRhvMPgAuo,3256
|
|
101
|
-
onnx2tf/ops/MaxPool.py,sha256=
|
|
101
|
+
onnx2tf/ops/MaxPool.py,sha256=0zO5gNfcwAhXAVxIbURiXAlLQ56oJvtpcyLm4leZVsM,17948
|
|
102
102
|
onnx2tf/ops/MaxRoiPool.py,sha256=RYZyjnINqJd6k7KLFJ-D9iHjA2vR-m7WvhrumD9cmDk,8490
|
|
103
103
|
onnx2tf/ops/MaxUnpool.py,sha256=dGIEvC45rFuWoeG1S9j4sjEdEUqiWs_xdY5DZH6X7b4,5743
|
|
104
104
|
onnx2tf/ops/Mean.py,sha256=xfTjKpQntJB8uXAkgDLS77oLXy2yQh1Hlz0K2GltMh0,3132
|
|
@@ -160,7 +160,7 @@ onnx2tf/ops/STFT.py,sha256=LDKN309_dBu4v9AYpz70uMJbNjRFiOte9O3wUL4bIJw,4463
|
|
|
160
160
|
onnx2tf/ops/ScaleAndTranslate.py,sha256=VQDDhSs9TyMLQy0mF7n8pZ2TuvoKY-Lhlzd7Inf4UdI,11989
|
|
161
161
|
onnx2tf/ops/Scan.py,sha256=hfN-DX6Gp-dG5158WMoHRrDWZAra3VSbsjsiphNqRIQ,16293
|
|
162
162
|
onnx2tf/ops/Scatter.py,sha256=5_rTM60FPCq8unyNPDO-BZXcuz6w9Uyl2Xqx-zJTpgg,746
|
|
163
|
-
onnx2tf/ops/ScatterElements.py,sha256=
|
|
163
|
+
onnx2tf/ops/ScatterElements.py,sha256=vXURNSkorfm7iQ_HA5vY9nf6YIkrAdqmB7sdEtWnUCo,10452
|
|
164
164
|
onnx2tf/ops/ScatterND.py,sha256=-mVbxXjQor2T6HVHSJy5e0FHQmEfaHknaKPuSc3Oz4o,11005
|
|
165
165
|
onnx2tf/ops/Selu.py,sha256=CD0SqQlTTe0chO7lebkrdfDFSk6Cg9zLhvrKomsSH4Y,3799
|
|
166
166
|
onnx2tf/ops/SequenceAt.py,sha256=jpjl9gVJFagtg223YY26I0pUUEgEFjJGvSZWwbo2-mQ,3278
|
|
@@ -199,19 +199,19 @@ onnx2tf/ops/Tile.py,sha256=xkprg6yTaykivcHFJ644opzVPctaeplu-Ed-OpS98Gg,12720
|
|
|
199
199
|
onnx2tf/ops/TopK.py,sha256=f6OG-DcMWneXwSjIkmY935SPyOMD5tMteHnlQHoJwQo,6348
|
|
200
200
|
onnx2tf/ops/Transpose.py,sha256=GwJFp7zVqodEsv5mGWviuFqeK93uVM7dbRQ1N8Ua1hg,9774
|
|
201
201
|
onnx2tf/ops/Trilu.py,sha256=uz2TgdErpo9GDp9n4PCe0_koIpNLgBoPCjv3A6VBTl8,4789
|
|
202
|
-
onnx2tf/ops/Unique.py,sha256=
|
|
202
|
+
onnx2tf/ops/Unique.py,sha256=Dms8Pi3uo8dyBFBddc-83_x4JSJ21pbaWhxzXzYotr4,6507
|
|
203
203
|
onnx2tf/ops/Unsqueeze.py,sha256=UJun_DYfg7aQaHoeAvWlB85oRtDWq2lP7kvb0njcaC0,12219
|
|
204
204
|
onnx2tf/ops/Upsample.py,sha256=SX3N_wZHD8G5Z0PLcPgX1ZCzOdct-uTzxKeMhhzeBOw,5304
|
|
205
205
|
onnx2tf/ops/Where.py,sha256=MaCcY9g4mKZQqCgh4xtoylicP-xVu9f4boKiu_q9Ow8,7711
|
|
206
206
|
onnx2tf/ops/Xor.py,sha256=2ceqxHSI1Wtez_CIh8gFfvcu45Xboqfyp1iy3v2vuIs,4590
|
|
207
207
|
onnx2tf/ops/__init__.py,sha256=jnmUWWa-3dHzBZV9bmPzXu6eoz2dumJTzO7i8JdcgSM,25
|
|
208
208
|
onnx2tf/utils/__init__.py,sha256=E9FM9He68VIASDnYp-OrxvHFVn55GzWqw2OEkCqn1zg,27
|
|
209
|
-
onnx2tf/utils/common_functions.py,sha256=
|
|
209
|
+
onnx2tf/utils/common_functions.py,sha256=ioOk2F5KnCepNW3FBt_4sAnsq_Jld0w0p8fbhWfuR2w,268342
|
|
210
210
|
onnx2tf/utils/enums.py,sha256=7c5TqetqB07VjyHoxJHfLgtqBqk9ZRyUF33fPOJR1IM,1649
|
|
211
211
|
onnx2tf/utils/iterative_json_optimizer.py,sha256=qqeIxWGxrhcCYk8-ebWnblnOkzDCwi-nseipHzHR_bk,10436
|
|
212
212
|
onnx2tf/utils/json_auto_generator.py,sha256=OC-SfKtUg7zUxaXTAg6kT0ShzIc3ByjDa3FNp173DtA,60302
|
|
213
213
|
onnx2tf/utils/logging.py,sha256=yUCmPuJ_XiUItM3sZMcaMO24JErkQy7zZwVTYWAuiKg,1982
|
|
214
|
-
onnx2tf-1.29.
|
|
215
|
-
onnx2tf-1.29.
|
|
216
|
-
onnx2tf-1.29.
|
|
217
|
-
onnx2tf-1.29.
|
|
214
|
+
onnx2tf-1.29.22.dist-info/WHEEL,sha256=fAguSjoiATBe7TNBkJwOjyL1Tt4wwiaQGtNtjRPNMQA,80
|
|
215
|
+
onnx2tf-1.29.22.dist-info/entry_points.txt,sha256=GuhvLu7ZlYECumbmoiFlKX0mFPtFi_Ti9L-E5yuQqKs,42
|
|
216
|
+
onnx2tf-1.29.22.dist-info/METADATA,sha256=eWX7J2epMkaxU8YW1paH77k23Lw7K4i_7WsJC9f6-is,156067
|
|
217
|
+
onnx2tf-1.29.22.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|