Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
17 changes: 9 additions & 8 deletions dnnv/_manage/linux/verifiers/nnenum.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,15 @@ def run(self, env: Environment, dependency: Dependency):
"pip install --upgrade pip",
(
"pip install"
' "numpy>=1.19,<1.22"'
' "onnx>=1.8,<1.11"'
' "onnxruntime>=1.7,<1.11"'
' "scipy>=1.4.1<1.8"'
' "threadpoolctl==2.1.0"'
' "skl2onnx==1.7.0"'
' "swiglpk"'
' "termcolor"'
' "numpy>=1.21,<1.24"'
' "onnx>=1.10,<1.12"'
' "onnxruntime>=1.10,<1.13"'
' "scipy>=1.7<1.10"'
' "threadpoolctl==3.1.0"'
' "skl2onnx==1.12"'
' "swiglpk~=5.0"'
' "termcolor==1.1.0"'
' "packaging==21.3"'
' "protobuf<=3.20"'
),
f"cd {cache_dir}",
Expand Down
1 change: 1 addition & 0 deletions dnnv/_manage/linux/verifiers/verinet.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def run(self, env: Environment, dependency: Dependency):
"pip install"
' "numba>=0.50,<0.60"'
' "onnx>=1.8,<1.11"'
' "protobuf<=3.20"'
' "torch>=1.8,<1.9"'
' "torchvision>=0.9,<0.10"'
),
Expand Down
86 changes: 85 additions & 1 deletion dnnv/nn/converters/onnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
from ..visitors import OperationVisitor


class OnnxConverterError(Exception):
pass


def convert(op_graph: OperationGraph, *, add_missing_optional_inputs=False):
converter = OnnxConverter(
op_graph, add_missing_optional_inputs=add_missing_optional_inputs
Expand Down Expand Up @@ -79,7 +83,7 @@ def _to_onnx_proto(
) -> Union[onnx.NodeProto, onnx.TensorProto, onnx.ValueInfoProto]:
if isinstance(value, Operation):
return self.visit(value)
if isinstance(value, np.ndarray):
if isinstance(value, (np.ndarray, np.number)):
tensor_proto = onnx.numpy_helper.from_array(value, name=opname)
self.initializer.append(tensor_proto)
return tensor_proto
Expand Down Expand Up @@ -184,6 +188,29 @@ def visit_Cast(self, operation: operations.Cast) -> onnx.NodeProto:

return node

def visit_Clip(self, operation: operations.Clip) -> onnx.NodeProto:
op_type = str(operation)
idx = self.op_counts[op_type] = self.op_counts[op_type] + 1
opname = f"{op_type}_{idx}"

x = self._to_onnx_proto(operation.x, f"{opname}.x")
inputs = [x.name]
if operation.min is not None:
min = self._to_onnx_proto(operation.min, f"{opname}.min")
inputs.extend([min.name])
if operation.max is not None:
max = self._to_onnx_proto(operation.max, f"{opname}.max")
inputs.extend([max.name])

node = onnx.helper.make_node(
op_type,
inputs=inputs,
outputs=[opname],
name=opname,
)

return node

def visit_Concat(self, operation: operations.Concat) -> onnx.NodeProto:
idx = self.op_counts["Concat"] = self.op_counts["Concat"] + 1
opname = f"Concat_{idx}"
Expand Down Expand Up @@ -508,6 +535,26 @@ def visit_OutputSelect(self, operation: operations.OutputSelect) -> onnx.NodePro

return node

def visit_ReduceL2(self, operation: operations.ReduceL2) -> onnx.NodeProto:
op_type = str(operation)
idx = self.op_counts[op_type] = self.op_counts[op_type] + 1
opname = f"{op_type}_{idx}"

x = self._to_onnx_proto(operation.x, f"{opname}.x")
axes = operation.axes
keepdims = operation.keepdims

node = onnx.helper.make_node(
op_type,
inputs=[x.name],
axes=axes,
keepdims=keepdims,
outputs=[opname],
name=opname,
)

return node

def visit_Relu(self, operation: operations.Relu) -> onnx.NodeProto:
idx = self.op_counts["Relu"] = self.op_counts["Relu"] + 1
opname = f"Relu_{idx}"
Expand Down Expand Up @@ -576,6 +623,25 @@ def visit_Split(self, operation: operations.Split) -> onnx.NodeProto:

return node

def visit_Squeeze(self, operation: operations.Squeeze) -> onnx.NodeProto:
op_type = str(operation)
idx = self.op_counts[op_type] = self.op_counts[op_type] + 1
opname = f"{op_type}_{idx}"

x = self._to_onnx_proto(operation.x, f"{opname}.x")
inputs = [x.name]
if operation.axes is not None:
axes = self._to_onnx_proto(operation.axes, f"{opname}.axes")
if axes.data_type != onnx.TensorProto.INT64:
raise OnnxConverterError("Squeeze axes should be int64.")
inputs.extend([axes.name])

node = onnx.helper.make_node(
op_type, inputs=inputs, outputs=[opname], name=opname
)

return node

def visit_Slice(self, operation: operations.Slice) -> onnx.NodeProto:
op_type = str(operation)
idx = self.op_counts[op_type] = self.op_counts[op_type] + 1
Expand Down Expand Up @@ -648,3 +714,21 @@ def visit_Transpose(self, operation: operations.Transpose) -> onnx.NodeProto:
)

return node

def visit_Upsample(self, operation: operations.Upsample) -> onnx.NodeProto:
op_type = str(operation)
idx = self.op_counts[op_type] = self.op_counts[op_type] + 1
opname = f"{op_type}_{idx}"

x = self._to_onnx_proto(operation.x, f"{opname}.x")
inputs = [x.name]
if operation.scales is not None:
scales = self._to_onnx_proto(operation.scales, f"{opname}.scales")
inputs.extend([scales.name])
mode = operation.mode

node = onnx.helper.make_node(
op_type, inputs=inputs, outputs=[opname], name=opname, mode=mode
)

return node
82 changes: 77 additions & 5 deletions dnnv/nn/converters/tensorflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,21 @@ def cast_func(*inputs):

return cast_func

def visit_Clip(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
_min = operation.min
_max = operation.max

@self._cached
def clip_func(*inputs):
x = _concretize([x_], inputs)
x = tf.clip_by_value(x, _min, _max)
return x

return clip_func

def visit_Concat(self, operation):
tensors_ = []
for x in operation.x:
Expand All @@ -227,20 +242,24 @@ def visit_Conv(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
w_ = operation.w
if isinstance(w_, Operation):
w_ = self.visit(w_)

@self._cached
def conv_func(*inputs):
x = _concretize([x_], inputs)
x, weights = _concretize([x_, w_], inputs)
if len(operation.kernel_shape) != 2:
raise NotImplementedError(
"Non 2d convolutions are not currently supported."
)
weights = operation.w
if not isinstance(weights, np.ndarray):
weights = np.array(weights)
Comment on lines +256 to +257

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? What can it be other than a numpy array? Could it be weights = np.asarray(weights) instead of the if block?

if operation.b is not None:
bias = operation.b
else:
bias = np.zeros((weights.shape[0],), dtype=weights.dtype)
assert np.all(operation.dilations == 1)
assert np.all(operation.group == 1)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? Could you add support for this parameter, similar to dilations?

num_pads = len(operation.pads)
pads = tuple(
zip(
Expand All @@ -258,6 +277,7 @@ def conv_func(*inputs):
weights.transpose((2, 3, 1, 0)),
operation.strides,
padding="VALID",
dilations=operation.dilations,
),
bias,
)
Expand Down Expand Up @@ -417,11 +437,13 @@ def visit_Expand(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
shape_ = operation.shape
if isinstance(shape_, Operation):
shape_ = self.visit(shape_)

@self._cached
def expand_func(*inputs):
x = _concretize([x_], inputs)
shape = operation.shape
x, shape = _concretize([x_, shape_], inputs)
result = x * tf.ones(shape, x.dtype)
return result

Expand Down Expand Up @@ -685,6 +707,21 @@ def pad_func(*inputs):

return pad_func

def visit_ReduceL2(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
axes = operation.axes

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axes is an input, so it could be an operation. Could you add a check for that and also add it to the _concretize function call below?

keepdims = operation.keepdims

@self._cached
def reduceL2_func(*inputs):
x = _concretize([x_], inputs)
x = tf.norm(x, ord=2, axis=axes, keepdims=keepdims)
return x

return reduceL2_func

def visit_Relu(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
Expand Down Expand Up @@ -738,6 +775,7 @@ def resize_func(*inputs):
assert operation.coordinate_transformation_mode in [
"asymmetric",
"tf_crop_and_resize",
"align_corners",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this okay to do? Please add tests for this new behavior.

]
assert operation.mode in ["nearest", "linear"]
assert operation.exclude_outside == 0
Expand Down Expand Up @@ -873,6 +911,22 @@ def split_func(*inputs):

return split_func

def visit_Squeeze(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
axes = operation.axes
if isinstance(axes, np.ndarray):
axes = axes.tolist()

@self._cached
def squeeze_func(*inputs):
x = _concretize([x_], inputs)
x = tf.squeeze(x, axis=axes)
return x

return squeeze_func

def visit_Sub(self, operation):
a_ = operation.a
if isinstance(a_, Operation):
Expand Down Expand Up @@ -948,3 +1002,21 @@ def unsqueeze_func(*inputs):
return x

return unsqueeze_func

def visit_Upsample(self, operation):
x_ = operation.x
if isinstance(x_, Operation):
x_ = self.visit(x_)
scales = operation.scales
mode = operation.mode

@self._cached
def upsample_func(*inputs):
x = _concretize([x_], inputs)
scaled_dim = [int(sd) for sd in x.shape[2:] * scales[2:]]
xr = tf.transpose(x, perm=[0, 2, 3, 1])
xr = tf.image.resize(xr, scaled_dim, method="nearest")
xr = tf.transpose(xr, perm=[0, 3, 1, 2])
return xr

return upsample_func
63 changes: 63 additions & 0 deletions dnnv/nn/operations/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ def from_onnx(cls, onnx_node, *inputs):
return cls(*inputs, to=to, name=onnx_node.name)


class Clip(Operation):
def __init__(self, x, min=None, max=None, *, name: Optional[str] = None):
super().__init__(name=name)
self.x = x
self.min = min
self.max = max

@classmethod
def from_onnx(cls, onnx_node, *inputs):
attributes = {a.name: as_numpy(a) for a in onnx_node.attribute}
if len(inputs) == 1:
_min = attributes.get("min")
_max = attributes.get("max")
return cls(*inputs, min=_min, max=_max, name=onnx_node.name)
return cls(*inputs, name=onnx_node.name)


class Concat(Operation):
def __init__(self, x, axis, *, name: Optional[str] = None):
super().__init__(name=name)
Expand Down Expand Up @@ -256,6 +273,48 @@ def from_onnx(cls, onnx_node, *inputs):
return cls(*inputs, axes=axes, name=onnx_node.name)


class ReduceL2(Operation):
def __init__(self, x, axes, keepdims, *, name: Optional[str] = None):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axes and keepdims are attributes, so they should go on the right of * and have provided defaults.

Suggested change
def __init__(self, x, axes, keepdims, *, name: Optional[str] = None):
def __init__(self, x, *, axes=None, keepdims=True, name: Optional[str] = None):

super().__init__(name=name)
self.x = x
self.axes = axes
self.keepdims = keepdims

@classmethod
def from_onnx(cls, onnx_node, *inputs):
attributes = {a.name: as_numpy(a) for a in onnx_node.attribute}
axes = attributes.get("axes")
keepdims = attributes.get("keepdims")

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keepdims should have a provided default value

Suggested change
keepdims = attributes.get("keepdims")
keepdims = attributes.get("keepdims", True)

return cls(*inputs, axes=axes, keepdims=keepdims, name=onnx_node.name)


class Squeeze(Operation):
def __init__(self, x, axes, *, name: Optional[str] = None):
super().__init__(name=name)
self.x = x
self.axes = axes

@classmethod
def from_onnx(cls, onnx_node, *inputs):
attributes = {a.name: as_numpy(a) for a in onnx_node.attribute}
axes = attributes.get("axes")
return cls(*inputs, axes=axes, name=onnx_node.name)
Comment on lines +300 to +301

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axes has been an input since version 13, so we shouldn't get its value from the attribute dict unless we have a model from an earlier version. A quick and dirty method is to check the number of inputs.

Suggested change
axes = attributes.get("axes")
return cls(*inputs, axes=axes, name=onnx_node.name)
if len(inputs) == 1:
axes = attributes.get("axes")
return cls(*inputs, axes=axes, name=onnx_node.name)
return cls(*inputs, name=onnx_node.name)



class Upsample(Operation):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment stating that this operation is deprecated. Maybe it should also give a warning, but I think a comment is sufficient for now.

Suggested change
class Upsample(Operation):
class Upsample(Operation):
```DEPRECATED```

def __init__(self, x, scales, mode, *, name: Optional[str] = None):
super().__init__(name=name)
self.x = x
self.scales = scales
self.mode = mode

@classmethod
def from_onnx(cls, onnx_node, *inputs):
attributes = {a.name: as_numpy(a) for a in onnx_node.attribute}
mode = attributes.get("mode")
return cls(*inputs, mode=mode, name=onnx_node.name)


__all__ = [
"Cast",
"Concat",
Expand All @@ -272,4 +331,8 @@ def from_onnx(cls, onnx_node, *inputs):
"Tile",
"Transpose",
"Unsqueeze",
"ReduceL2",
"Clip",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you put these in alphabetical order :)

"Squeeze",
"Upsample",
]
Loading