New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
make primary ops function step 2 #4512
Conversation
…e simple function? Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
This PR will have a significant impact on converting librairies. |
Yes it does. For updated ops, I have added appropriate adapters for model converter. Anything else I need to do? |
@BowenBao : for your awareness: this PR proposes to promote the axes attribute to input for various Reduce* ops. (This was already done for ReduceSum previously, but not for other ops.) I hope the impact on the converter won't be much? It would be good to know, thanks. |
all_function_opset_versions.end(), | ||
context_dependent_function_opset_versions.begin(), | ||
context_dependent_function_opset_versions.end()); | ||
std::sort(all_function_opset_versions.begin(), all_function_opset_versions.end()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a possibility the result contains duplicates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, added code to remove duplications. op authors shall not define both type of functions with the same version. all_function_opset_versions is used to check whether there is any function so we generate doc properly.
noop_with_empty_axes = noop_with_empty_axes or self.noop_with_empty_axes # type: ignore | ||
return self._run(data, axes, keepdims, noop_with_empty_axes) | ||
|
||
def _run(self, data, axes, keepdims=1, noop_with_empty_axes=0): # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may remove def run
and define the default values for def _run
as None. If None, the main class automatically looks into the node definition to replace them or the schema if they are not specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But is this wrong? I find this current form useful since a reader can understand what happens (what is the default value) without having to look elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xadupre , I agree with @gramalingam here. We need at least on call (_run here) to follow precisely the op schema signature. (I was thinking that _run does not have to use None as default for op attributes. It turned out it is not true)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition, I think the suggested _run signature does not work with nest calls - when the op is part of other function ops. There for now I like to keep the current code.
@@ -294,6 +295,10 @@ class ShapeInferenceImplBase { | |||
} else if (attr.type() == AttributeProto::SPARSE_TENSOR && attr.has_sparse_tensor()) { | |||
input_sparse_data_by_name[n.output(0)] = &attr.sparse_tensor(); | |||
} | |||
} else if (attr.name() == "value_ints" && attr.type() == AttributeProto::INTS) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would put attr.type() == AttributeProto::INTS
first. Both conditions should be equivalent but checking an int is faster than a string.
@@ -60,6 +60,26 @@ | |||
# not implemented | |||
"test__simple_gradient_of_add", # gradient not implemented | |||
"test__simple_gradient_of_add_and_mul", # gradient not implemented | |||
"test_layer_normalization_2d_axis1_expanded", # https://github.com/onnx/onnx/issues/4653 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://github.com/onnx/onnx/blob/main/onnx/backend/test/case/node/split.py#L13, I followed the convention defined in this file. You can specify an opset with suffix _opset<opset>
. This might not fully answer issue #4653. The test test_layer_normalization_4d_axis_negative_3_expanded_ver18
should work if the function is written with operator defined in opset 18.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right that every test ending with _ver18 shall pass. I enabled those tests.
regarding naming of expended tests, they are auto generated. I used _ver# for opset import for the function body.
@@ -461,7 +481,7 @@ class TestOnnxBackEndWithReferenceEvaluator(unittest.TestCase): | |||
|
|||
@classmethod | |||
def add_test_methods(cls): | |||
for folder in ["node", "pytorch-converted", "pytorch-operator", "simple"]: | |||
for folder in ["node"]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My mistake. I commented for debugging. Forgot to bring it back.
for te in enumerate_onnx_tests("node", lambda folder: folder == name): | ||
code.append(te) | ||
self.assertEqual(len(code), 1) | ||
# def test_onnx_backend_test_abs(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why disabling the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forgot bring it back after debugging
onnx/defs/schema.cc
Outdated
else | ||
return false; | ||
bool OpSchema::BuildContextDependentFunction(const FunctionBodyBuildContext& ctx, FunctionProto& function_proto) const { | ||
return BuildContextDependentFunctionWithOpsetVersion(ctx, function_proto, since_version_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Do we need these two different methods? Or, can we just use BuildContextDependentFunction
with a default value for parameter requested_opset_version?
onnx/defs/schema.cc
Outdated
} | ||
|
||
const FunctionProto* OpSchema::GetFunctionWithOpsetVersion(int requested_opset_version) const { | ||
return const_cast<OpSchema*>(this)->GetFunctionWithOpsetInternal(requested_opset_version); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this const_cast
? May be outdated. I don't see any UpdateFunctionProtoOpsetImportVersion any more. May be simpler to inline the internal function here.
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some nit comments. I saw some test_data_set_0 only has input_1.pb without input_0.pb. Is it expected? Since this PR really introduces a lot of test files (model.onnx, input/output pb files), it would be super great if we can verify them in ORT in advance.
onnx/test/shape_inference_test.py
Outdated
@@ -2309,24 +2310,70 @@ def test_gemm_no_bias(self) -> None: | |||
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (13, 17))] | |||
) | |||
|
|||
def test_reduce_op_shape_2_axis(self) -> None: | |||
def test_reduce_op_shape_2_axis_opset_version_13(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could we keep the test name short like test_reduce_op_shape_2_axis_opset13
?
Existing test uses something like test_scan_opset9
actually.
onnx_ai_opset_imports = [ | ||
oi | ||
for oi in kwargs["opset_imports"] | ||
if oi.domain == "" or oi.domain == "ai.onnx" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny nit: more pythonic.
if oi.domain == "" or oi.domain == "ai.onnx" | |
if oi.domain in ("", "ai.onnx") |
Signed-off-by: Liqun Fu <liqfu@microsoft.com>
That is because due to the test change, only input1 is changed. input0 stays unchanged so it is not in the PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Only nit comment left is whether HasContextDependentFunctionWithOpsetVersion
can be renamed HasContextDependentFunction
.
Description
update reduce ops so that axes is the second optional input
change reduce ops to function op
Motivation and Context
to reduce amount of primary ops